From 0a3ebdda6b0098934e5edc770b24771044dbe4e1 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 2 Feb 2026 20:59:30 +0100 Subject: [PATCH 1/2] Added animations for the snake's face --- assets/images/eye_lid.png | Bin 0 -> 5550 bytes assets/images/snake_mouth.png | Bin 0 -> 30691 bytes ...deathAnimation.hpp => death_animation.hpp} | 0 .../chomper/animations/head_animation.hpp | 60 ++++++++++ lib/include/chomper/animator.hpp | 1 + lib/include/chomper/player.hpp | 18 ++- lib/include/chomper/runtimes/game.hpp | 7 +- ...deathAnimation.cpp => death_animation.cpp} | 2 +- lib/src/animations/head_animation.cpp | 107 ++++++++++++++++++ lib/src/animator.cpp | 4 + lib/src/player.cpp | 8 +- lib/src/runtimes/game.cpp | 2 +- 12 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 assets/images/eye_lid.png create mode 100644 assets/images/snake_mouth.png rename lib/include/chomper/animations/{deathAnimation.hpp => death_animation.hpp} (100%) create mode 100644 lib/include/chomper/animations/head_animation.hpp rename lib/src/animations/{deathAnimation.cpp => death_animation.cpp} (97%) create mode 100644 lib/src/animations/head_animation.cpp diff --git a/assets/images/eye_lid.png b/assets/images/eye_lid.png new file mode 100644 index 0000000000000000000000000000000000000000..7899cb7eb570986b3e58c9a649bf8bf2a866e80e GIT binary patch literal 5550 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 aSB0oz=~qQ6ZK2HH*@4rJ*0m?G@&5(3_y*ws literal 0 HcmV?d00001 diff --git a/assets/images/snake_mouth.png b/assets/images/snake_mouth.png new file mode 100644 index 0000000000000000000000000000000000000000..3e6bcbe67b99d1ca5d47682bafff40925fa71819 GIT binary patch literal 30691 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 literal 0 HcmV?d00001 diff --git a/lib/include/chomper/animations/deathAnimation.hpp b/lib/include/chomper/animations/death_animation.hpp similarity index 100% rename from lib/include/chomper/animations/deathAnimation.hpp rename to lib/include/chomper/animations/death_animation.hpp diff --git a/lib/include/chomper/animations/head_animation.hpp b/lib/include/chomper/animations/head_animation.hpp new file mode 100644 index 0000000..b9fb476 --- /dev/null +++ b/lib/include/chomper/animations/head_animation.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "chomper/animator.hpp" +#include "chomper/controller.hpp" +#include "klib/base_types.hpp" +#include "klib/log.hpp" +#include "klib/ptr.hpp" +#include "le2d/drawable/sprite.hpp" +#include "le2d/render_instance.hpp" +#include + +namespace chomper { +class Engine; + +namespace animation { +class DirectionProvider : public klib::Polymorphic { + public: + [[nodiscard]] virtual glm::vec2 getHeadPosition() const = 0; + [[nodiscard]] virtual Heading getHeading() const = 0; +}; + +class CollectibleProvider : public klib::Polymorphic { + public: + [[nodiscard]] virtual std::span getCollectibles() const = 0; +}; + +class Eye { + public: + explicit Eye(le::ITexture& eyeLidTexture); + void setPosition(glm::vec2 position); + void movePupil(glm::vec2 target); + void draw(le::IRenderer& renderer, bool drawEyeLids) const; + + private: + le::drawable::Sprite m_eyeLid{}; + le::drawable::Circle m_eye{}; + le::drawable::Circle m_pupil{}; +}; + +class HeadAnimation : public IAnimation { + public: + explicit HeadAnimation(klib::Ptr directionProvider, klib::Ptr collectibleProvider, + gsl::not_null engine); + void tick(kvf::Seconds dt) final; + void draw(le::IRenderer& renderer) const final; + + private: + klib::TypedLogger m_log; + + klib::Ptr m_directionProvider{}; + klib::Ptr m_collectibleProvider{}; + + le::drawable::Sprite m_mouth{}; + std::unique_ptr m_leftEye{}; + std::unique_ptr m_rightEye{}; + klib::Ptr m_eyeLidTexture{}; + + bool m_shouldDraw{}; +}; +} // namespace animation +} // namespace chomper diff --git a/lib/include/chomper/animator.hpp b/lib/include/chomper/animator.hpp index ec9fbfd..6b64c89 100644 --- a/lib/include/chomper/animator.hpp +++ b/lib/include/chomper/animator.hpp @@ -23,6 +23,7 @@ class Animator { void play(std::unique_ptr animation); void tick(kvf::Seconds dt); + void stopAll(); void draw(le::IRenderer& renderer) const; diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index 0e5f60d..e80aff8 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -1,4 +1,5 @@ #pragma once +#include "chomper/animations/head_animation.hpp" #include "chomper/animator.hpp" #include "chomper/controller.hpp" #include "chomper/debug_inspector.hpp" @@ -14,14 +15,18 @@ namespace chomper { class Engine; -class Player : public IController::IListener, public IDebugInspector, public klib::Pinned { +namespace runtime { +class Game; +} + +class Player : public IController::IListener, public IDebugInspector, public klib::Pinned, public animation::DirectionProvider { public: struct Info { bool alive = true; int score{}; }; - explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine); + explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine, gsl::not_null game); void tick(kvf::Seconds dt); void draw(le::IRenderer& renderer) const; @@ -35,6 +40,15 @@ class Player : public IController::IListener, public IDebugInspector, public kli return m_snake.getSegments(); } + [[nodiscard]] glm::vec2 getHeadPosition() const final { + KLIB_ASSERT(!m_snake.getSegments().empty()); + return m_snake.getSegments().back().transform.position; + } + + [[nodiscard]] Heading getHeading() const final { + return m_heading; + } + private: [[nodiscard]] bool isCollidingWithSelf(glm::ivec2 targetGrid) const; [[nodiscard]] bool isCollidingWithWall(glm::ivec2 targetGrid) const; diff --git a/lib/include/chomper/runtimes/game.hpp b/lib/include/chomper/runtimes/game.hpp index bd4f851..7dbcc21 100644 --- a/lib/include/chomper/runtimes/game.hpp +++ b/lib/include/chomper/runtimes/game.hpp @@ -1,4 +1,5 @@ #pragma once +#include "chomper/animations/head_animation.hpp" #include "chomper/collectibles.hpp" #include "chomper/engine.hpp" #include "chomper/player.hpp" @@ -15,10 +16,14 @@ namespace chomper::runtime { // driven by Engine, owner (whether indirectly) of all game things. -class Game : public IRuntime, public klib::Pinned { +class Game : public IRuntime, public klib::Pinned, public animation::CollectibleProvider { public: explicit Game(gsl::not_null engine); + [[nodiscard]] std::span getCollectibles() const final { + return m_collectibles->getInstances(); + } + private: // all Game-level input actions. struct Actions { diff --git a/lib/src/animations/deathAnimation.cpp b/lib/src/animations/death_animation.cpp similarity index 97% rename from lib/src/animations/deathAnimation.cpp rename to lib/src/animations/death_animation.cpp index b285937..c5e9aa8 100644 --- a/lib/src/animations/deathAnimation.cpp +++ b/lib/src/animations/death_animation.cpp @@ -1,4 +1,4 @@ -#include "chomper/animations/deathAnimation.hpp" +#include "chomper/animations/death_animation.hpp" #include "chomper/world_space.hpp" #include diff --git a/lib/src/animations/head_animation.cpp b/lib/src/animations/head_animation.cpp new file mode 100644 index 0000000..5e45ac9 --- /dev/null +++ b/lib/src/animations/head_animation.cpp @@ -0,0 +1,107 @@ +#include "chomper/animations/head_animation.hpp" +#include "chomper/engine.hpp" +#include "chomper/world_space.hpp" +#include "klib/enum_array.hpp" + +namespace chomper::animation { +namespace { +constexpr klib::EnumArray headingToRot_v{glm::radians(90.f), glm::radians(0.f), glm::radians(270.f), glm::radians(180.f)}; + +constexpr auto isAdjacent(glm::ivec2 const& grid1, glm::ivec2 const& grid2) { + auto dx = std::abs(grid1.x - grid2.x); + auto dy = std::abs(grid1.y - grid2.y); + + return (dx <= 1 && dy <= 1) && (dx != 0 || dy != 0); +} + +std::optional findAdjacentCollectible(std::span collectibles, glm::ivec2 headPos) { + auto it = std::ranges::find_if(collectibles, [&](auto const& c) { + return isAdjacent(headPos, worldSpace::worldToGrid(c.transform.position)); + }); + if (it == collectibles.end()) { + return std::nullopt; + } + return it->transform.position; +} + +} // namespace + +HeadAnimation::HeadAnimation(klib::Ptr directionProvider, klib::Ptr collectibleProvider, + gsl::not_null engine) + : m_directionProvider(directionProvider), m_collectibleProvider(collectibleProvider) { + m_mouth.set_base_size(tileSize_v); + m_mouth.set_texture(engine->getResources().load("images/snake_mouth.png")); + + m_eyeLidTexture = engine->getResources().load("images/eye_lid.png"); + m_leftEye = std::make_unique(*m_eyeLidTexture); + m_rightEye = std::make_unique(*m_eyeLidTexture); +} + +void HeadAnimation::tick(kvf::Seconds /*dt*/) { + auto headPos = m_directionProvider->getHeadPosition(); + auto heading = m_directionProvider->getHeading(); + + auto c = std::cos(headingToRot_v[heading]); + auto s = std::sin(headingToRot_v[heading]); + auto rotation = glm::mat2(c, -s, s, c); + + auto rightEyeOffset = rotation * glm::vec2{tileSize_v.x * 0.22f, tileSize_v.y * -0.22f}; + m_rightEye->setPosition(headPos + rightEyeOffset); + + auto leftEyeOffset = rotation * glm::vec2{tileSize_v.x * -0.22f, tileSize_v.y * -0.22f}; + m_leftEye->setPosition(headPos + leftEyeOffset); + + auto target = findAdjacentCollectible(m_collectibleProvider->getCollectibles(), worldSpace::worldToGrid(headPos)); + if (!target) { + m_shouldDraw = false; + return; + } + m_shouldDraw = true; + + auto mouthOffset = rotation * glm::vec2{0.f, tileSize_v.x * 0.5f}; + m_mouth.transform.position = headPos + mouthOffset; + m_mouth.transform.orientation = le::nvec2::from_radians(headingToRot_v[heading]); + + m_leftEye->movePupil(*target); + m_rightEye->movePupil(*target); +} + +void HeadAnimation::draw(le::IRenderer& renderer) const { + m_leftEye->draw(renderer, !m_shouldDraw); + m_rightEye->draw(renderer, !m_shouldDraw); + + if (!m_shouldDraw) { + return; + } + m_mouth.draw(renderer); +} + +Eye::Eye(le::ITexture& texture) { + m_eye.create(tileSize_v.x * 0.4f); + m_pupil.create(m_eye.get_diameter() * 0.5f); + m_pupil.tint = kvf::Color{glm::vec4{0.f, 0.f, 0.f, 1.f}}; + m_eyeLid.set_base_size(m_eye.get_size()); + + m_eyeLid.set_texture(&texture); +} + +void Eye::setPosition(glm::vec2 position) { + m_eyeLid.transform.position = position; + m_eye.transform.position = position; + m_pupil.transform.position = position; +} + +void Eye::movePupil(glm::vec2 target) { + auto dir = glm::normalize(target - m_eye.transform.position); + m_pupil.transform.position = m_eye.transform.position + dir * m_eye.get_diameter() * 0.25f; +} + +void Eye::draw(le::IRenderer& renderer, bool drawEyeLids) const { + m_eye.draw(renderer); + m_pupil.draw(renderer); + if (drawEyeLids) { + m_eyeLid.draw(renderer); + } +} + +} // namespace chomper::animation \ No newline at end of file diff --git a/lib/src/animator.cpp b/lib/src/animator.cpp index 3cab6bf..59977b1 100644 --- a/lib/src/animator.cpp +++ b/lib/src/animator.cpp @@ -14,6 +14,10 @@ void Animator::tick(kvf::Seconds dt) { } } +void Animator::stopAll() { + m_playing.clear(); +} + void Animator::draw(le::IRenderer& renderer) const { for (auto const& animation : m_playing) { animation->draw(renderer); diff --git a/lib/src/player.cpp b/lib/src/player.cpp index f536de3..d845867 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -1,7 +1,8 @@ #include "chomper/player.hpp" -#include "chomper/animations/deathAnimation.hpp" +#include "chomper/animations/death_animation.hpp" #include "chomper/controllers/player_controller.hpp" #include "chomper/engine.hpp" +#include "chomper/runtimes/game.hpp" #include "chomper/world_size.hpp" #include "chomper/world_space.hpp" #include @@ -13,9 +14,11 @@ constexpr auto oppositeHeading_v = klib::EnumArray{Heading::We constexpr auto headingToDir_v = klib::EnumArray{glm::ivec2{1, 0}, glm::ivec2{0, 1}, glm::ivec2{-1, 0}, glm::ivec2{0, -1}}; } // namespace -Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine) : m_engine(engine) { +Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine, gsl::not_null game) : m_engine(engine) { createController(mapping); updateScoreText(); + + m_animator.play(std::make_unique(this, game, engine)); } void Player::tick(kvf::Seconds dt) { @@ -71,6 +74,7 @@ void Player::move() { if (isCollidingWithSelf(targetGrid) || isCollidingWithWall(targetGrid)) { if (m_graceMove) { m_info.alive = false; + m_animator.stopAll(); m_animator.play(std::make_unique(m_snake.getSegments())); } else { m_graceMove = true; diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 62dcf5d..051685b 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -86,7 +86,7 @@ void Game::createPlayer() { // rebind game actions. bindActions(); // create the player, passing a reference of the logger and a reference of the input mapping to create its PlayerController. - m_player = std::make_unique(m_mapping, m_engine); + m_player = std::make_unique(m_mapping, m_engine, this); } void Game::createCollectibles() { From f43fb65581c801f7b1641b1a7b89edafd9507cc8 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 3 Feb 2026 15:51:56 +0100 Subject: [PATCH 2/2] Added requested changes --- .../chomper/animations/head_animation.hpp | 10 ++++----- lib/include/chomper/controller.hpp | 13 +----------- lib/include/chomper/heading.hpp | 21 +++++++++++++++++++ lib/include/chomper/player.hpp | 7 ++----- lib/src/animations/head_animation.cpp | 17 ++++++++------- lib/src/player.cpp | 9 ++++---- lib/src/snake.cpp | 4 ---- 7 files changed, 42 insertions(+), 39 deletions(-) create mode 100644 lib/include/chomper/heading.hpp diff --git a/lib/include/chomper/animations/head_animation.hpp b/lib/include/chomper/animations/head_animation.hpp index b9fb476..3042418 100644 --- a/lib/include/chomper/animations/head_animation.hpp +++ b/lib/include/chomper/animations/head_animation.hpp @@ -1,6 +1,6 @@ #pragma once #include "chomper/animator.hpp" -#include "chomper/controller.hpp" +#include "chomper/heading.hpp" #include "klib/base_types.hpp" #include "klib/log.hpp" #include "klib/ptr.hpp" @@ -27,7 +27,7 @@ class Eye { public: explicit Eye(le::ITexture& eyeLidTexture); void setPosition(glm::vec2 position); - void movePupil(glm::vec2 target); + void lookAt(glm::vec2 target); void draw(le::IRenderer& renderer, bool drawEyeLids) const; private: @@ -38,7 +38,7 @@ class Eye { class HeadAnimation : public IAnimation { public: - explicit HeadAnimation(klib::Ptr directionProvider, klib::Ptr collectibleProvider, + explicit HeadAnimation(gsl::not_null directionProvider, gsl::not_null collectibleProvider, gsl::not_null engine); void tick(kvf::Seconds dt) final; void draw(le::IRenderer& renderer) const final; @@ -46,8 +46,8 @@ class HeadAnimation : public IAnimation { private: klib::TypedLogger m_log; - klib::Ptr m_directionProvider{}; - klib::Ptr m_collectibleProvider{}; + gsl::not_null m_directionProvider; + gsl::not_null m_collectibleProvider; le::drawable::Sprite m_mouth{}; std::unique_ptr m_leftEye{}; diff --git a/lib/include/chomper/controller.hpp b/lib/include/chomper/controller.hpp index 81fb40d..2af5c84 100644 --- a/lib/include/chomper/controller.hpp +++ b/lib/include/chomper/controller.hpp @@ -1,22 +1,11 @@ #pragma once +#include "chomper/heading.hpp" #include #include #include -#include #include -#include namespace chomper { -// represents a direction. -enum class Heading : std::int8_t { - East, - North, - West, - South, - COUNT_ -}; -constexpr auto headingName_v = klib::EnumArray{"East", "North", "West", "South"}; - // Heading controller interface. class IController : public klib::Polymorphic { public: diff --git a/lib/include/chomper/heading.hpp b/lib/include/chomper/heading.hpp new file mode 100644 index 0000000..b086652 --- /dev/null +++ b/lib/include/chomper/heading.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include + +namespace chomper { +// represents a direction. +enum class Heading : std::int8_t { + East, + North, + West, + South, + COUNT_ +}; + +constexpr auto headingName_v = klib::EnumArray{"East", "North", "West", "South"}; +constexpr auto oppositeHeading_v = klib::EnumArray{Heading::West, Heading::South, Heading::East, Heading::North}; +constexpr auto headingToDir_v = klib::EnumArray{glm::ivec2{1, 0}, glm::ivec2{0, 1}, glm::ivec2{-1, 0}, glm::ivec2{0, -1}}; +constexpr klib::EnumArray headingToRot_v{glm::radians(90.f), glm::radians(0.f), glm::radians(270.f), glm::radians(180.f)}; + +} // namespace chomper \ No newline at end of file diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index e80aff8..7ab3d14 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -15,10 +15,6 @@ namespace chomper { class Engine; -namespace runtime { -class Game; -} - class Player : public IController::IListener, public IDebugInspector, public klib::Pinned, public animation::DirectionProvider { public: struct Info { @@ -26,7 +22,8 @@ class Player : public IController::IListener, public IDebugInspector, public kli int score{}; }; - explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine, gsl::not_null game); + explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine, + gsl::not_null collectibleProvider); void tick(kvf::Seconds dt); void draw(le::IRenderer& renderer) const; diff --git a/lib/src/animations/head_animation.cpp b/lib/src/animations/head_animation.cpp index 5e45ac9..13c146c 100644 --- a/lib/src/animations/head_animation.cpp +++ b/lib/src/animations/head_animation.cpp @@ -5,11 +5,9 @@ namespace chomper::animation { namespace { -constexpr klib::EnumArray headingToRot_v{glm::radians(90.f), glm::radians(0.f), glm::radians(270.f), glm::radians(180.f)}; - constexpr auto isAdjacent(glm::ivec2 const& grid1, glm::ivec2 const& grid2) { - auto dx = std::abs(grid1.x - grid2.x); - auto dy = std::abs(grid1.y - grid2.y); + auto dx = klib::abs(grid1.x - grid2.x); + auto dy = klib::abs(grid1.y - grid2.y); return (dx <= 1 && dy <= 1) && (dx != 0 || dy != 0); } @@ -26,13 +24,16 @@ std::optional findAdjacentCollectible(std::span directionProvider, klib::Ptr collectibleProvider, +HeadAnimation::HeadAnimation(gsl::not_null directionProvider, gsl::not_null collectibleProvider, gsl::not_null engine) : m_directionProvider(directionProvider), m_collectibleProvider(collectibleProvider) { m_mouth.set_base_size(tileSize_v); m_mouth.set_texture(engine->getResources().load("images/snake_mouth.png")); m_eyeLidTexture = engine->getResources().load("images/eye_lid.png"); + if (!m_eyeLidTexture) { + return; + } m_leftEye = std::make_unique(*m_eyeLidTexture); m_rightEye = std::make_unique(*m_eyeLidTexture); } @@ -62,8 +63,8 @@ void HeadAnimation::tick(kvf::Seconds /*dt*/) { m_mouth.transform.position = headPos + mouthOffset; m_mouth.transform.orientation = le::nvec2::from_radians(headingToRot_v[heading]); - m_leftEye->movePupil(*target); - m_rightEye->movePupil(*target); + m_leftEye->lookAt(*target); + m_rightEye->lookAt(*target); } void HeadAnimation::draw(le::IRenderer& renderer) const { @@ -91,7 +92,7 @@ void Eye::setPosition(glm::vec2 position) { m_pupil.transform.position = position; } -void Eye::movePupil(glm::vec2 target) { +void Eye::lookAt(glm::vec2 target) { auto dir = glm::normalize(target - m_eye.transform.position); m_pupil.transform.position = m_eye.transform.position + dir * m_eye.get_diameter() * 0.25f; } diff --git a/lib/src/player.cpp b/lib/src/player.cpp index d845867..603b1a6 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -2,7 +2,6 @@ #include "chomper/animations/death_animation.hpp" #include "chomper/controllers/player_controller.hpp" #include "chomper/engine.hpp" -#include "chomper/runtimes/game.hpp" #include "chomper/world_size.hpp" #include "chomper/world_space.hpp" #include @@ -10,15 +9,15 @@ namespace chomper { namespace { constexpr auto moveSpeed_v = kvf::Seconds{0.135f}; -constexpr auto oppositeHeading_v = klib::EnumArray{Heading::West, Heading::South, Heading::East, Heading::North}; -constexpr auto headingToDir_v = klib::EnumArray{glm::ivec2{1, 0}, glm::ivec2{0, 1}, glm::ivec2{-1, 0}, glm::ivec2{0, -1}}; } // namespace -Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine, gsl::not_null game) : m_engine(engine) { +Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine, + gsl::not_null collectibleProvider) + : m_engine(engine) { createController(mapping); updateScoreText(); - m_animator.play(std::make_unique(this, game, engine)); + m_animator.play(std::make_unique(this, collectibleProvider, engine)); } void Player::tick(kvf::Seconds dt) { diff --git a/lib/src/snake.cpp b/lib/src/snake.cpp index 7c24c01..60fa718 100644 --- a/lib/src/snake.cpp +++ b/lib/src/snake.cpp @@ -7,10 +7,6 @@ #include namespace chomper { -namespace { -constexpr auto headingToDir_v = klib::EnumArray{glm::ivec2{1, 0}, glm::ivec2{0, 1}, glm::ivec2{-1, 0}, glm::ivec2{0, -1}}; -} // namespace - Snake::Snake() { le::RenderInstance instance{}; instance.tint = theme::snakeBodyColor_v;