diff --git a/lib/include/chomper/animations/deathAnimation.hpp b/lib/include/chomper/animations/deathAnimation.hpp new file mode 100644 index 0000000..be38ffb --- /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{}; + }; + + std::vector m_segments{}; + le::drawable::InstancedQuad m_quads{}; + + 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..ec9fbfd --- /dev/null +++ b/lib/include/chomper/animator.hpp @@ -0,0 +1,32 @@ +#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; + + [[nodiscard]] bool finished() const { + return m_finished; + } + + protected: + bool m_finished{}; +}; + +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..b285937 --- /dev/null +++ b/lib/src/animations/deathAnimation.cpp @@ -0,0 +1,84 @@ +#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()); + 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); + 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; + + 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) { + 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; + } + + seg.remaining -= dt.count(); + + auto t = std::clamp(1.f - (seg.remaining / seg.lifetime), 0.f, 1.f); + + auto eased = 1.f - easeOut(t); + + 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; + } + + auto angle = dt.count() * eased * seg.rotSpeed; + quad.transform.orientation.rotate(angle); + + if (t > 0.8f) { + 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; + } + } + m_finished = !animating; +} + +void DeathAnimation::draw(le::IRenderer& renderer) const { + m_quads.draw(renderer); +} + +} // namespace chomper::animation diff --git a/lib/src/animator.cpp b/lib/src/animator.cpp new file mode 100644 index 0000000..3cab6bf --- /dev/null +++ b/lib/src/animator.cpp @@ -0,0 +1,22 @@ +#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) { + std::erase_if(m_playing, [&](auto const& animation) { + return animation->finished(); + }); + 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..cc89cbe 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -40,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); + } } }