From 7c7f974207beb768ca39085628249c6c0b366bb8 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 20 Jan 2026 21:15:32 +0100 Subject: [PATCH 1/2] animator animator --- .../chomper/animations/deathAnimation.hpp | 23 ++++++ lib/include/chomper/animator.hpp | 25 +++++++ lib/include/chomper/player.hpp | 3 + lib/src/animations/deathAnimation.cpp | 75 +++++++++++++++++++ lib/src/animator.cpp | 19 +++++ lib/src/player.cpp | 10 ++- lib/src/runtimes/game.cpp | 3 +- 7 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 lib/include/chomper/animations/deathAnimation.hpp create mode 100644 lib/include/chomper/animator.hpp create mode 100644 lib/src/animations/deathAnimation.cpp create mode 100644 lib/src/animator.cpp diff --git a/lib/include/chomper/animations/deathAnimation.hpp b/lib/include/chomper/animations/deathAnimation.hpp new file mode 100644 index 0000000..01113e3 --- /dev/null +++ b/lib/include/chomper/animations/deathAnimation.hpp @@ -0,0 +1,23 @@ +#include "chomper/animator.hpp" + +namespace chomper::animation { +class DeathAnimation : public chomper::IAnimation { + public: + DeathAnimation(std::span instances); + void tick(kvf::Seconds dt) final; + void draw(le::IRenderer& renderer) const final; + + private: + struct Segment { + float remaining{}; + float lifetime{}; + float rotSpeed{}; + glm::vec2 velocity{}; + le::drawable::Quad quad{}; + }; + + std::vector m_segments{}; + + le::Random m_random{}; +}; +} // namespace chomper::animation \ No newline at end of file diff --git a/lib/include/chomper/animator.hpp b/lib/include/chomper/animator.hpp new file mode 100644 index 0000000..9495b50 --- /dev/null +++ b/lib/include/chomper/animator.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include + +namespace chomper { +class IAnimation : public klib::Polymorphic { + public: + virtual void tick(kvf::Seconds dt) = 0; + virtual void draw(le::IRenderer& renderer) const = 0; +}; + +class Animator { + public: + void play(std::unique_ptr animation); + + void tick(kvf::Seconds dt); + + void draw(le::IRenderer& renderer) const; + + private: + std::vector> m_playing{}; +}; +} // namespace chomper \ No newline at end of file diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index 7320a3d..b507e1b 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -1,4 +1,5 @@ #pragma once +#include "chomper/animator.hpp" #include "chomper/controller.hpp" #include "chomper/debug_inspector.hpp" #include "chomper/snake.hpp" @@ -66,5 +67,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli // bool to decide wether to remove the tail, turn false if the snake has eaten bool m_shouldPop = true; bool m_graceMove{}; + + Animator m_animator{}; }; } // namespace chomper diff --git a/lib/src/animations/deathAnimation.cpp b/lib/src/animations/deathAnimation.cpp new file mode 100644 index 0000000..bf9a5c6 --- /dev/null +++ b/lib/src/animations/deathAnimation.cpp @@ -0,0 +1,75 @@ +#include "chomper/animations/deathAnimation.hpp" +#include "chomper/world_space.hpp" +#include + +namespace chomper::animation { +namespace { +constexpr auto easeOut(float t) { + return 1.f - ((1.f - t) * (1.f - t)); +} +} // namespace + +DeathAnimation::DeathAnimation(std::span instances) { + m_segments.clear(); + m_segments.reserve(instances.size()); + + for (auto const& instance : instances) { + auto dir = m_random.next_float(0, 360); + auto lifetime = m_random.next_float(2, 5); + auto speed = m_random.next_float(300, 600); + auto rotSpeed = m_random.next_float(5.f, 20.f); + + auto rad = dir * std::numbers::pi_v / 180.f; + + Segment seg; + seg.rotSpeed = rotSpeed; + seg.velocity = {std::cos(rad) * speed, std::sin(rad) * speed}; + seg.remaining = lifetime; + seg.lifetime = lifetime; + seg.quad.create(tileSize_v); + seg.quad.tint = instance.tint; + seg.quad.transform = instance.transform; + m_segments.push_back(seg); + } +} +void DeathAnimation::tick(kvf::Seconds dt) { + for (auto& seg : m_segments) { + if (seg.remaining <= 0.f) { + continue; + } + + seg.remaining -= dt.count(); + + auto t = std::clamp(1.f - (seg.remaining / seg.lifetime), 0.f, 1.f); + + auto eased = 1.f - easeOut(t); + + seg.quad.transform.position += seg.velocity * eased * dt.count(); + if (worldSpace::isOutOfBounds(worldSpace::worldToGrid(seg.quad.transform.position))) { + seg.velocity = -seg.velocity; + } + + float angle = dt.count() * eased * seg.rotSpeed; + auto o = seg.quad.transform.orientation; + auto cos = std::cos(angle); + auto sin = std::sin(angle); + seg.quad.transform.orientation = {(o.x * cos) - (o.y * sin), (o.x * sin) + (o.y * cos)}; + + float scale = 1.f; + if (t > 0.8f) { + float u = (t - 0.8f) / 0.2f; // 0 → 1 + scale = 1.f - u; // 1 → 0 + } + seg.quad.transform.scale = {scale, scale}; + } +} + +void DeathAnimation::draw(le::IRenderer& renderer) const { + for (auto const& segment : m_segments) { + if (segment.remaining > 0.f) { + segment.quad.draw(renderer); + } + } +} + +} // namespace chomper::animation diff --git a/lib/src/animator.cpp b/lib/src/animator.cpp new file mode 100644 index 0000000..517f52a --- /dev/null +++ b/lib/src/animator.cpp @@ -0,0 +1,19 @@ +#include "chomper/animator.hpp" + +namespace chomper { +void Animator::play(std::unique_ptr animation) { + m_playing.emplace_back(std::move(animation)); +} + +void Animator::tick(kvf::Seconds dt) { + for (auto const& animation : m_playing) { + animation->tick(dt); + } +} + +void Animator::draw(le::IRenderer& renderer) const { + for (auto const& animation : m_playing) { + animation->draw(renderer); + } +} +} // namespace chomper \ No newline at end of file diff --git a/lib/src/player.cpp b/lib/src/player.cpp index e381e74..c345ce7 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -1,4 +1,5 @@ #include "chomper/player.hpp" +#include "chomper/animations/deathAnimation.hpp" #include "chomper/controllers/player_controller.hpp" #include "chomper/engine.hpp" #include "chomper/world_size.hpp" @@ -18,12 +19,13 @@ Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_nulltick(dt); - m_moveTimer += dt; if (m_moveTimer >= moveSpeed_v) { @@ -69,6 +71,7 @@ void Player::move() { if (isCollidingWithSelf(targetGrid) || isCollidingWithWall(targetGrid)) { if (m_graceMove) { m_info.alive = false; + m_animator.play(std::make_unique(m_snake.getSegments())); } else { m_graceMove = true; } @@ -96,7 +99,10 @@ void Player::updateScoreText() { } void Player::draw(le::IRenderer& renderer) const { - m_snake.draw(renderer); + if (m_info.alive) { + m_snake.draw(renderer); + } + m_animator.draw(renderer); m_scoreText.draw(renderer); } diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index fbae186..a2410b1 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -1,6 +1,5 @@ #include "chomper/runtimes/game.hpp" #include "chomper/im_util.hpp" -#include "chomper/runtimes/entrypoint.hpp" #include "chomper/world_space.hpp" #include #include @@ -40,7 +39,7 @@ void Game::tick(kvf::Seconds const dt) { // On death if (!m_player->getInfo().alive) { - m_engine->setNextRuntime(); + // m_engine->setNextRuntime(); } } From 3b9b7c23b477e220952394c9ad72062230a2c15c Mon Sep 17 00:00:00 2001 From: = Date: Wed, 21 Jan 2026 11:16:13 +0100 Subject: [PATCH 2/2] add requested changes --- .../chomper/animations/deathAnimation.hpp | 2 +- lib/include/chomper/animator.hpp | 7 +++ lib/src/animations/deathAnimation.cpp | 49 +++++++++++-------- lib/src/animator.cpp | 3 ++ lib/src/runtimes/game.cpp | 11 ++++- 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/lib/include/chomper/animations/deathAnimation.hpp b/lib/include/chomper/animations/deathAnimation.hpp index 01113e3..be38ffb 100644 --- a/lib/include/chomper/animations/deathAnimation.hpp +++ b/lib/include/chomper/animations/deathAnimation.hpp @@ -13,10 +13,10 @@ class DeathAnimation : public chomper::IAnimation { float lifetime{}; float rotSpeed{}; glm::vec2 velocity{}; - le::drawable::Quad quad{}; }; std::vector m_segments{}; + le::drawable::InstancedQuad m_quads{}; le::Random m_random{}; }; diff --git a/lib/include/chomper/animator.hpp b/lib/include/chomper/animator.hpp index 9495b50..ec9fbfd 100644 --- a/lib/include/chomper/animator.hpp +++ b/lib/include/chomper/animator.hpp @@ -9,6 +9,13 @@ class IAnimation : public klib::Polymorphic { public: virtual void tick(kvf::Seconds dt) = 0; virtual void draw(le::IRenderer& renderer) const = 0; + + [[nodiscard]] bool finished() const { + return m_finished; + } + + protected: + bool m_finished{}; }; class Animator { diff --git a/lib/src/animations/deathAnimation.cpp b/lib/src/animations/deathAnimation.cpp index bf9a5c6..b285937 100644 --- a/lib/src/animations/deathAnimation.cpp +++ b/lib/src/animations/deathAnimation.cpp @@ -12,6 +12,9 @@ constexpr auto easeOut(float t) { DeathAnimation::DeathAnimation(std::span instances) { m_segments.clear(); m_segments.reserve(instances.size()); + m_quads.instances.clear(); + m_quads.instances.reserve(instances.size()); + m_quads.create(tileSize_v); for (auto const& instance : instances) { auto dir = m_random.next_float(0, 360); @@ -26,14 +29,21 @@ DeathAnimation::DeathAnimation(std::span instances) { seg.velocity = {std::cos(rad) * speed, std::sin(rad) * speed}; seg.remaining = lifetime; seg.lifetime = lifetime; - seg.quad.create(tileSize_v); - seg.quad.tint = instance.tint; - seg.quad.transform = instance.transform; + + m_quads.instances.emplace_back().tint = instance.tint; + m_quads.instances.back().transform = instance.transform; + m_segments.push_back(seg); } } + void DeathAnimation::tick(kvf::Seconds dt) { - for (auto& seg : m_segments) { + assert(m_segments.size() == m_quads.instances.size()); + + auto animating = false; + + for (std::size_t i = 0; i < m_segments.size(); i++) { + auto& seg = m_segments[i]; if (seg.remaining <= 0.f) { continue; } @@ -44,32 +54,31 @@ void DeathAnimation::tick(kvf::Seconds dt) { auto eased = 1.f - easeOut(t); - seg.quad.transform.position += seg.velocity * eased * dt.count(); - if (worldSpace::isOutOfBounds(worldSpace::worldToGrid(seg.quad.transform.position))) { + auto& quad = m_quads.instances.at(i); + quad.transform.position += seg.velocity * eased * dt.count(); + if (worldSpace::isOutOfBounds(worldSpace::worldToGrid(quad.transform.position))) { seg.velocity = -seg.velocity; } - float angle = dt.count() * eased * seg.rotSpeed; - auto o = seg.quad.transform.orientation; - auto cos = std::cos(angle); - auto sin = std::sin(angle); - seg.quad.transform.orientation = {(o.x * cos) - (o.y * sin), (o.x * sin) + (o.y * cos)}; + auto angle = dt.count() * eased * seg.rotSpeed; + quad.transform.orientation.rotate(angle); - float scale = 1.f; if (t > 0.8f) { - float u = (t - 0.8f) / 0.2f; // 0 → 1 - scale = 1.f - u; // 1 → 0 + auto scale = 1.f; + auto u = (t - 0.8f) / 0.2f; // 0 → 1 + scale = 1.f - u; // 1 → 0 + quad.transform.scale = {scale, scale}; + } + + if (t < 1.f) { + animating = true; } - seg.quad.transform.scale = {scale, scale}; } + m_finished = !animating; } void DeathAnimation::draw(le::IRenderer& renderer) const { - for (auto const& segment : m_segments) { - if (segment.remaining > 0.f) { - segment.quad.draw(renderer); - } - } + m_quads.draw(renderer); } } // namespace chomper::animation diff --git a/lib/src/animator.cpp b/lib/src/animator.cpp index 517f52a..3cab6bf 100644 --- a/lib/src/animator.cpp +++ b/lib/src/animator.cpp @@ -6,6 +6,9 @@ void Animator::play(std::unique_ptr animation) { } void Animator::tick(kvf::Seconds dt) { + std::erase_if(m_playing, [&](auto const& animation) { + return animation->finished(); + }); for (auto const& animation : m_playing) { animation->tick(dt); } diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index a2410b1..cc89cbe 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -1,5 +1,6 @@ #include "chomper/runtimes/game.hpp" #include "chomper/im_util.hpp" +#include "chomper/runtimes/entrypoint.hpp" #include "chomper/world_space.hpp" #include #include @@ -39,7 +40,15 @@ void Game::tick(kvf::Seconds const dt) { // On death if (!m_player->getInfo().alive) { - // m_engine->setNextRuntime(); + auto const visitor = klib::SubVisitor{[this](le::event::Key const& key) { + if (key.action != GLFW_PRESS) { + return; + } + m_engine->setNextRuntime(); + }}; + for (auto const& event : m_engine->getContext().event_queue()) { + std::visit(visitor, event); + } } }