Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions lib/include/chomper/animations/deathAnimation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "chomper/animator.hpp"

namespace chomper::animation {
class DeathAnimation : public chomper::IAnimation {
public:
DeathAnimation(std::span<le::RenderInstance const> 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<Segment> m_segments{};
le::drawable::InstancedQuad m_quads{};

le::Random m_random{};
};
} // namespace chomper::animation
32 changes: 32 additions & 0 deletions lib/include/chomper/animator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once
#include <kvf/time.hpp>
#include <le2d/drawable/shape.hpp>
#include <le2d/random.hpp>
#include <le2d/render_instance.hpp>

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<IAnimation> animation);

void tick(kvf::Seconds dt);

void draw(le::IRenderer& renderer) const;

private:
std::vector<std::unique_ptr<IAnimation>> m_playing{};
};
} // namespace chomper
3 changes: 3 additions & 0 deletions lib/include/chomper/player.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include "chomper/animator.hpp"
#include "chomper/controller.hpp"
#include "chomper/debug_inspector.hpp"
#include "chomper/snake.hpp"
Expand Down Expand Up @@ -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
84 changes: 84 additions & 0 deletions lib/src/animations/deathAnimation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "chomper/animations/deathAnimation.hpp"
#include "chomper/world_space.hpp"
#include <numbers>

namespace chomper::animation {
namespace {
constexpr auto easeOut(float t) {
return 1.f - ((1.f - t) * (1.f - t));
}
} // namespace

DeathAnimation::DeathAnimation(std::span<le::RenderInstance const> 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<float> / 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
22 changes: 22 additions & 0 deletions lib/src/animator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "chomper/animator.hpp"

namespace chomper {
void Animator::play(std::unique_ptr<IAnimation> 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
10 changes: 8 additions & 2 deletions lib/src/player.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -18,12 +19,13 @@ Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null<Engine con
}

void Player::tick(kvf::Seconds dt) {
m_animator.tick(dt);

if (!m_info.alive) {
return;
}

m_controller->tick(dt);

m_moveTimer += dt;

if (m_moveTimer >= moveSpeed_v) {
Expand Down Expand Up @@ -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<animation::DeathAnimation>(m_snake.getSegments()));
} else {
m_graceMove = true;
}
Expand Down Expand Up @@ -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);
}

Expand Down
10 changes: 9 additions & 1 deletion lib/src/runtimes/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ void Game::tick(kvf::Seconds const dt) {

// On death
if (!m_player->getInfo().alive) {
m_engine->setNextRuntime<runtime::Entrypoint>();
auto const visitor = klib::SubVisitor{[this](le::event::Key const& key) {
if (key.action != GLFW_PRESS) {
return;
}
m_engine->setNextRuntime<Entrypoint>();
}};
for (auto const& event : m_engine->getContext().event_queue()) {
std::visit(visitor, event);
}
}
}

Expand Down