Skip to content

Commit ec45a72

Browse files
authored
Add Animator, IAnimation, DeathAnimation (#30)
1 parent b096144 commit ec45a72

7 files changed

Lines changed: 181 additions & 3 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include "chomper/animator.hpp"
2+
3+
namespace chomper::animation {
4+
class DeathAnimation : public chomper::IAnimation {
5+
public:
6+
DeathAnimation(std::span<le::RenderInstance const> instances);
7+
void tick(kvf::Seconds dt) final;
8+
void draw(le::IRenderer& renderer) const final;
9+
10+
private:
11+
struct Segment {
12+
float remaining{};
13+
float lifetime{};
14+
float rotSpeed{};
15+
glm::vec2 velocity{};
16+
};
17+
18+
std::vector<Segment> m_segments{};
19+
le::drawable::InstancedQuad m_quads{};
20+
21+
le::Random m_random{};
22+
};
23+
} // namespace chomper::animation

lib/include/chomper/animator.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#pragma once
2+
#include <kvf/time.hpp>
3+
#include <le2d/drawable/shape.hpp>
4+
#include <le2d/random.hpp>
5+
#include <le2d/render_instance.hpp>
6+
7+
namespace chomper {
8+
class IAnimation : public klib::Polymorphic {
9+
public:
10+
virtual void tick(kvf::Seconds dt) = 0;
11+
virtual void draw(le::IRenderer& renderer) const = 0;
12+
13+
[[nodiscard]] bool finished() const {
14+
return m_finished;
15+
}
16+
17+
protected:
18+
bool m_finished{};
19+
};
20+
21+
class Animator {
22+
public:
23+
void play(std::unique_ptr<IAnimation> animation);
24+
25+
void tick(kvf::Seconds dt);
26+
27+
void draw(le::IRenderer& renderer) const;
28+
29+
private:
30+
std::vector<std::unique_ptr<IAnimation>> m_playing{};
31+
};
32+
} // namespace chomper

lib/include/chomper/player.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
2+
#include "chomper/animator.hpp"
23
#include "chomper/controller.hpp"
34
#include "chomper/debug_inspector.hpp"
45
#include "chomper/snake.hpp"
@@ -66,5 +67,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli
6667
// bool to decide wether to remove the tail, turn false if the snake has eaten
6768
bool m_shouldPop = true;
6869
bool m_graceMove{};
70+
71+
Animator m_animator{};
6972
};
7073
} // namespace chomper
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include "chomper/animations/deathAnimation.hpp"
2+
#include "chomper/world_space.hpp"
3+
#include <numbers>
4+
5+
namespace chomper::animation {
6+
namespace {
7+
constexpr auto easeOut(float t) {
8+
return 1.f - ((1.f - t) * (1.f - t));
9+
}
10+
} // namespace
11+
12+
DeathAnimation::DeathAnimation(std::span<le::RenderInstance const> instances) {
13+
m_segments.clear();
14+
m_segments.reserve(instances.size());
15+
m_quads.instances.clear();
16+
m_quads.instances.reserve(instances.size());
17+
m_quads.create(tileSize_v);
18+
19+
for (auto const& instance : instances) {
20+
auto dir = m_random.next_float(0, 360);
21+
auto lifetime = m_random.next_float(2, 5);
22+
auto speed = m_random.next_float(300, 600);
23+
auto rotSpeed = m_random.next_float(5.f, 20.f);
24+
25+
auto rad = dir * std::numbers::pi_v<float> / 180.f;
26+
27+
Segment seg;
28+
seg.rotSpeed = rotSpeed;
29+
seg.velocity = {std::cos(rad) * speed, std::sin(rad) * speed};
30+
seg.remaining = lifetime;
31+
seg.lifetime = lifetime;
32+
33+
m_quads.instances.emplace_back().tint = instance.tint;
34+
m_quads.instances.back().transform = instance.transform;
35+
36+
m_segments.push_back(seg);
37+
}
38+
}
39+
40+
void DeathAnimation::tick(kvf::Seconds dt) {
41+
assert(m_segments.size() == m_quads.instances.size());
42+
43+
auto animating = false;
44+
45+
for (std::size_t i = 0; i < m_segments.size(); i++) {
46+
auto& seg = m_segments[i];
47+
if (seg.remaining <= 0.f) {
48+
continue;
49+
}
50+
51+
seg.remaining -= dt.count();
52+
53+
auto t = std::clamp(1.f - (seg.remaining / seg.lifetime), 0.f, 1.f);
54+
55+
auto eased = 1.f - easeOut(t);
56+
57+
auto& quad = m_quads.instances.at(i);
58+
quad.transform.position += seg.velocity * eased * dt.count();
59+
if (worldSpace::isOutOfBounds(worldSpace::worldToGrid(quad.transform.position))) {
60+
seg.velocity = -seg.velocity;
61+
}
62+
63+
auto angle = dt.count() * eased * seg.rotSpeed;
64+
quad.transform.orientation.rotate(angle);
65+
66+
if (t > 0.8f) {
67+
auto scale = 1.f;
68+
auto u = (t - 0.8f) / 0.2f; // 0 → 1
69+
scale = 1.f - u; // 1 → 0
70+
quad.transform.scale = {scale, scale};
71+
}
72+
73+
if (t < 1.f) {
74+
animating = true;
75+
}
76+
}
77+
m_finished = !animating;
78+
}
79+
80+
void DeathAnimation::draw(le::IRenderer& renderer) const {
81+
m_quads.draw(renderer);
82+
}
83+
84+
} // namespace chomper::animation

lib/src/animator.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include "chomper/animator.hpp"
2+
3+
namespace chomper {
4+
void Animator::play(std::unique_ptr<IAnimation> animation) {
5+
m_playing.emplace_back(std::move(animation));
6+
}
7+
8+
void Animator::tick(kvf::Seconds dt) {
9+
std::erase_if(m_playing, [&](auto const& animation) {
10+
return animation->finished();
11+
});
12+
for (auto const& animation : m_playing) {
13+
animation->tick(dt);
14+
}
15+
}
16+
17+
void Animator::draw(le::IRenderer& renderer) const {
18+
for (auto const& animation : m_playing) {
19+
animation->draw(renderer);
20+
}
21+
}
22+
} // namespace chomper

lib/src/player.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "chomper/player.hpp"
2+
#include "chomper/animations/deathAnimation.hpp"
23
#include "chomper/controllers/player_controller.hpp"
34
#include "chomper/engine.hpp"
45
#include "chomper/world_size.hpp"
@@ -18,12 +19,13 @@ Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null<Engine con
1819
}
1920

2021
void Player::tick(kvf::Seconds dt) {
22+
m_animator.tick(dt);
23+
2124
if (!m_info.alive) {
2225
return;
2326
}
2427

2528
m_controller->tick(dt);
26-
2729
m_moveTimer += dt;
2830

2931
if (m_moveTimer >= moveSpeed_v) {
@@ -69,6 +71,7 @@ void Player::move() {
6971
if (isCollidingWithSelf(targetGrid) || isCollidingWithWall(targetGrid)) {
7072
if (m_graceMove) {
7173
m_info.alive = false;
74+
m_animator.play(std::make_unique<animation::DeathAnimation>(m_snake.getSegments()));
7275
} else {
7376
m_graceMove = true;
7477
}
@@ -96,7 +99,10 @@ void Player::updateScoreText() {
9699
}
97100

98101
void Player::draw(le::IRenderer& renderer) const {
99-
m_snake.draw(renderer);
102+
if (m_info.alive) {
103+
m_snake.draw(renderer);
104+
}
105+
m_animator.draw(renderer);
100106
m_scoreText.draw(renderer);
101107
}
102108

lib/src/runtimes/game.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ void Game::tick(kvf::Seconds const dt) {
4040

4141
// On death
4242
if (!m_player->getInfo().alive) {
43-
m_engine->setNextRuntime<runtime::Entrypoint>();
43+
auto const visitor = klib::SubVisitor{[this](le::event::Key const& key) {
44+
if (key.action != GLFW_PRESS) {
45+
return;
46+
}
47+
m_engine->setNextRuntime<Entrypoint>();
48+
}};
49+
for (auto const& event : m_engine->getContext().event_queue()) {
50+
std::visit(visitor, event);
51+
}
4452
}
4553
}
4654

0 commit comments

Comments
 (0)