From 1aa6674e4279535d21a07b52c1840eff4a4c2717 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Thu, 20 Mar 2025 01:10:01 -0400 Subject: [PATCH] Created a nice utility library for doing animations, and used it in the ritual crafting UI. --- Makefile | 2 +- animation.cpp | 103 +++++++++++++++++++++++++++++++++++ animation.hpp | 14 +++++ assets/animations.json | 12 ++++ assets/config.json | 3 +- assets/the_ritual_circle.png | Bin 0 -> 44512 bytes boss_fight_ui.cpp | 2 +- components.cpp | 58 -------------------- components.hpp | 2 +- main.cpp | 4 ++ meson.build | 3 + ritual_ui.cpp | 76 +++++++------------------- tests/animation.cpp | 53 ++++++++++++++++++ tests/components.cpp | 31 ----------- 14 files changed, 213 insertions(+), 150 deletions(-) create mode 100644 animation.cpp create mode 100644 animation.hpp create mode 100644 assets/animations.json create mode 100644 assets/the_ritual_circle.png create mode 100644 tests/animation.cpp diff --git a/Makefile b/Makefile index 562ac6d..a7354d5 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ tracy_build: meson compile -j 10 -C builddir test: build - ./builddir/runtests + ./builddir/runtests "[animation]" run: build test powershell "cp ./builddir/zedcaster.exe ." diff --git a/animation.cpp b/animation.cpp new file mode 100644 index 0000000..2e5856d --- /dev/null +++ b/animation.cpp @@ -0,0 +1,103 @@ +#include "animation.hpp" + +namespace components { + void Animation::play() { + if(!playing) { + current = 0; + subframe = 0.0f; + playing = true; + } + } + + float Animation::twitching() { + switch(easing) { + case ease::NONE: + return 0.0; + case ease::SINE: + return ease::sine(float(frames) / subframe * ease_rate); + case ease::OUT_CIRC: + return ease::out_circ(ease::sine(float(frames) / subframe * ease_rate)); + case ease::OUT_BOUNCE: + return ease::out_bounce(ease::sine(float(frames) / subframe * ease_rate)); + case ease::IN_OUT_BACK: + return ease::in_out_back(ease::sine(float(frames) / subframe * ease_rate)); + default: + dbc::sentinel( + fmt::format("Invalid easing {} given to animation", + int(easing))); + } + } + + void Animation::step(sf::Vector2f& scale_out, sf::Vector2f& pos_out, sf::IntRect& rect_out) { + if(playing && current < frames) { + float tick = twitching(); + scale_out.x = std::lerp(scale_out.x, scale_out.x + scale, tick); + scale_out.y = std::lerp(scale_out.y, scale_out.y + scale, tick); + + if(stationary) { + pos_out.y = pos_out.y - (pos_out.y * scale_out.y - pos_out.y); + } + + if(!simple) { + rect_out.position.x += current * frame_width; + } + + subframe += speed; + current = int(subframe); + } else if(!looped) { + playing = false; + current = frames - 1; + subframe = float(frames - 1); + + if(!simple) { + rect_out.position.x += current * frame_width; + } + } else { + playing = false; + current = 0; + subframe = 0.0f; + } + } +} + + +namespace animation { + using namespace components; + using namespace textures; + + static AnimationManager MGR; + static bool initialized = false; + + bool apply(Animation& anim, SpriteTexture& target) { + auto size = target.texture->getSize(); + anim.frame_width = int(size.x) / (unsigned int)anim.frames; + sf::IntRect rect{{0,0}, {anim.frame_width, int(size.y)}}; + sf::Vector2f scale{1.0, 1.0}; + sf::Vector2f pos{0, 0}; + + anim.step(scale, pos, rect); + + target.sprite->setTextureRect(rect); + target.sprite->setPosition(pos); + target.sprite->setScale(scale); + + return anim.playing; + } + + void init() { + if(!initialized) { + Config config("assets/animations.json"); + + for(auto& [name, data] : config.json().items()) { + auto anim = components::convert(data); + MGR.animations.insert_or_assign(name, anim); + } + + initialized = true; + } + } + + Animation load(std::string name) { + return MGR.animations.at(name); + } +} diff --git a/animation.hpp b/animation.hpp new file mode 100644 index 0000000..44bac0e --- /dev/null +++ b/animation.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "components.hpp" +#include "textures.hpp" +#include "easings.hpp" + +namespace animation { + struct AnimationManager { + std::unordered_map animations; + }; + + bool apply(components::Animation& anim, textures::SpriteTexture& target); + void init(); + components::Animation load(std::string name); +} diff --git a/assets/animations.json b/assets/animations.json new file mode 100644 index 0000000..bfcb8b8 --- /dev/null +++ b/assets/animations.json @@ -0,0 +1,12 @@ +{ + "ritual_blanket": { + "_type": "Animation", + "easing": 0, + "ease_rate": 0.5, + "scale": 1.0, + "simple": false, + "frames": 3, + "speed": 0.2, + "stationary": true + } +} diff --git a/assets/config.json b/assets/config.json index 8ae40f8..573cc22 100644 --- a/assets/config.json +++ b/assets/config.json @@ -52,7 +52,8 @@ "devils_fingers_stage": "assets/devils_fingers_stage.png", "tunnel_with_rocks": "assets/tunnel_with_rocks.png", "tunnel_with_rocks_stage": "assets/tunnel_with_rocks_stage.png", - "ritual_crafting_area": "assets/ritual_crafting_area.png" + "ritual_crafting_area": "assets/ritual_crafting_area.png", + "the_ritual_circle": "assets/the_ritual_circle.png" }, "worldgen": { "enemy_probability": 50, diff --git a/assets/the_ritual_circle.png b/assets/the_ritual_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..8f6d447b04cda971f3f5f53f277788e81acb29f8 GIT binary patch literal 44512 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ᷠ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 $animation = $world->get($boss_id); - $animation.texture_width = $sprite_config.width; + $animation.frame_width = $sprite_config.width; $boss_image = textures::get($sprite_config.name); sf::IntRect frame_rect{{0,0},{$sprite_config.width,$sprite_config.height}}; diff --git a/components.cpp b/components.cpp index 8898df0..65c84fe 100644 --- a/components.cpp +++ b/components.cpp @@ -28,62 +28,4 @@ namespace components { components::enroll(component_map); components::enroll(component_map); } - - void Animation::play() { - if(!playing) { - current = 0; - subframe = 0.0f; - playing = true; - } - } - - float Animation::twitching() { - switch(easing) { - case ease::NONE: - return 0.0; - case ease::SINE: - return ease::sine(float(frames) / subframe * ease_rate); - case ease::OUT_CIRC: - return ease::out_circ(ease::sine(float(frames) / subframe * ease_rate)); - case ease::OUT_BOUNCE: - return ease::out_bounce(ease::sine(float(frames) / subframe * ease_rate)); - case ease::IN_OUT_BACK: - return ease::in_out_back(ease::sine(float(frames) / subframe * ease_rate)); - default: - dbc::sentinel( - fmt::format("Invalid easing {} given to animation", - int(easing))); - } - } - - void Animation::step(sf::Vector2f& scale_out, sf::Vector2f& pos_out, sf::IntRect& rect_out) { - if(playing && current < frames) { - float tick = twitching(); - scale_out.x = std::lerp(scale_out.x, scale_out.x + scale, tick); - scale_out.y = std::lerp(scale_out.y, scale_out.y + scale, tick); - - if(stationary) { - pos_out.y = pos_out.y - (pos_out.y * scale_out.y - pos_out.y); - } - - if(!simple) { - rect_out.position.x += current * texture_width; - } - - subframe += speed; - current = int(subframe); - } else if(!looped) { - playing = false; - current = frames - 1; - subframe = float(frames - 1); - - if(!simple) { - rect_out.position.x += current * texture_width; - } - } else { - playing = false; - current = 0; - subframe = 0.0f; - } - } } diff --git a/components.hpp b/components.hpp index e68b3d5..23341c0 100644 --- a/components.hpp +++ b/components.hpp @@ -123,7 +123,7 @@ namespace components { float subframe = 0.0f; bool looped = false; // BUG: this is weirdly not used in most animations but also named wrong should be frame_width - int texture_width = TEXTURE_WIDTH; + int frame_width = TEXTURE_WIDTH; void play(); float twitching(); diff --git a/main.cpp b/main.cpp index bbb2867..f1f434f 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ #include "sound.hpp" #include "autowalker.hpp" #include "ai.hpp" +#include "animation.hpp" #include int main(int argc, char* argv[]) { @@ -10,6 +11,9 @@ int main(int argc, char* argv[]) { textures::init(); sound::init(); ai::init("assets/ai.json"); + animation::init(); + + sound::mute(true); gui::FSM main; main.event(gui::Event::STARTED); diff --git a/meson.build b/meson.build index 54c00da..86af5aa 100644 --- a/meson.build +++ b/meson.build @@ -79,6 +79,8 @@ dependencies += [ sources = [ 'ai.cpp', 'ai_debug.cpp', + 'animation.cpp', + 'animation.cpp', 'autowalker.cpp', 'boss_fight_ui.cpp', 'camera.cpp', @@ -138,6 +140,7 @@ executable('runtests', sources + [ 'tests/rituals.cpp', 'tests/sound.cpp', 'tests/spatialmap.cpp', + 'tests/animation.cpp', 'tests/stats.cpp', 'tests/textures.cpp', 'tests/tilemap.cpp', diff --git a/ritual_ui.cpp b/ritual_ui.cpp index 565ad33..e8d0e20 100644 --- a/ritual_ui.cpp +++ b/ritual_ui.cpp @@ -2,6 +2,7 @@ #include "components.hpp" #include "guecs.hpp" #include "rand.hpp" +#include "animation.hpp" namespace gui { using namespace guecs; @@ -13,12 +14,12 @@ namespace gui { $gui.position(STATUS_UI_X, STATUS_UI_Y, STATUS_UI_WIDTH, STATUS_UI_HEIGHT); $gui.layout( "[_]" - "[inv_slot5 | inv_slot6 | inv_slot7| inv_slot8]" "[inv_slot9 | inv_slot10 | inv_slot11| inv_slot12]" "[inv_slot13 | inv_slot14 | inv_slot15| inv_slot16]" "[inv_slot17 | inv_slot18 | inv_slot19| inv_slot20]" "[inv_slot21 | inv_slot22 | inv_slot23| inv_slot24]" - "[*%(100,500)circle_area]" + "[*%(100,600)circle_area]" + "[_]" "[_]" "[_]" "[_]" @@ -27,17 +28,17 @@ namespace gui { } void RitualUI::init() { - // $gui.world().set_the({$gui.$parser}); - for(auto& [name, cell] : $gui.cells()) { auto button = $gui.entity(name); if(name == "circle_area") { // $gui.set(button, {}); + $gui.set(button, {"the_ritual_circle"}); $gui.set(button, { [this](auto, auto){ dbc::log("circle clicked"); } }); } else if(name.starts_with("inv_slot")) { + $gui.set(button, {"the_ritual_circle"}); // $gui.set(button, {}); $gui.set(button, { [this, name](auto, auto){ dbc::log(fmt::format("inv_slot {}", name)); } @@ -53,13 +54,7 @@ namespace gui { $ritual_ui.sprite->setPosition({0,0}); $ritual_ui.sprite->setTextureRect($ritual_closed_rect); $ritual_state = RitualUIState::CLOSED; - $ritual_anim.simple = false; - $ritual_anim.looped = false; - $ritual_anim.easing = ease::NONE; - $ritual_anim.stationary = true; - $ritual_anim.texture_width = 380; - $ritual_anim.frames = 3; - $ritual_anim.speed = 0.2f; + $ritual_anim = animation::load("ritual_blanket"); $gui.init(); } @@ -75,20 +70,11 @@ namespace gui { void RitualUI::toggle() { using enum RitualUIState; - switch($ritual_state) { - case OPEN: - $ritual_state = CLOSING; - break; - case CLOSED: - $ritual_state = OPENING; - $ritual_anim.play(); - break; - case OPENING: // ignored - break; - case CLOSING: // ignored - break; - default: - dbc::sentinel("INVALID RitualUIState"); + if($ritual_state == OPEN) { + $ritual_state = CLOSING; + } else if($ritual_state == CLOSED) { + $ritual_state = OPENING; + $ritual_anim.play(); } } @@ -98,40 +84,16 @@ namespace gui { } void RitualUI::render(sf::RenderWindow &window) { - sf::IntRect rect; - sf::Vector2f scale{1.0, 1.0}; - sf::Vector2f pos{0, 0}; - using enum RitualUIState; - switch($ritual_state) { - case OPEN: { - rect = $ritual_open_rect; - } break; - case CLOSED: { - rect = $ritual_closed_rect; - } - break; - case OPENING: { - if($ritual_anim.playing) { - rect = $ritual_closed_rect; - $ritual_anim.step(scale, pos, rect); - } else { - $ritual_state = OPEN; - rect = $ritual_open_rect; - } - } - break; - case CLOSING: { - rect = $ritual_closed_rect; - $ritual_state = CLOSED; - } break; - default: - dbc::sentinel("INVALID RitualUIState"); - } - $ritual_ui.sprite->setTextureRect(rect); - $ritual_ui.sprite->setPosition(pos); - $ritual_ui.sprite->setScale(scale); + if($ritual_state == OPENING) { + if(!animation::apply($ritual_anim, $ritual_ui)) { + $ritual_state = OPEN; + } + } else if($ritual_state == CLOSING) { + $ritual_ui.sprite->setTextureRect($ritual_closed_rect); + $ritual_state = CLOSED; + } window.draw(*$ritual_ui.sprite); if($ritual_state == OPEN) $gui.render(window); diff --git a/tests/animation.cpp b/tests/animation.cpp new file mode 100644 index 0000000..d3fc991 --- /dev/null +++ b/tests/animation.cpp @@ -0,0 +1,53 @@ +#include +#include "animation.hpp" +#include "dinkyecs.hpp" +#include "config.hpp" +#include + +using namespace components; +using namespace textures; + +TEST_CASE("animation easing tests", "[animation]") { + Animation anim; + + anim.easing = ease::NONE; + float res = anim.twitching(); + REQUIRE(res == 0.0); + + anim.easing = ease::SINE; + anim.subframe = 1.0f; + res = anim.twitching(); + REQUIRE(!std::isnan(res)); + + anim.easing = ease::OUT_CIRC; + res = anim.twitching(); + REQUIRE(!std::isnan(res)); + + anim.easing = ease::OUT_BOUNCE; + res = anim.twitching(); + REQUIRE(!std::isnan(res)); + + anim.easing = ease::IN_OUT_BACK; + res = anim.twitching(); + REQUIRE(!std::isnan(res)); + + anim.easing = ease::FUCKFACE; + bool throws = false; + try { anim.twitching(); } catch(...) { throws = true; } + REQUIRE(throws); +} + + +TEST_CASE("animation utility API", "[animation]") { + textures::init(); + animation::init(); + + auto blanket = textures::get("ritual_crafting_area"); + auto anim = animation::load("ritual_blanket"); + + anim.play(); + + while(animation::apply(anim, blanket)) { + fmt::println("animation: {}", anim.subframe); + } +} diff --git a/tests/components.cpp b/tests/components.cpp index 5f95c23..bdca7e2 100644 --- a/tests/components.cpp +++ b/tests/components.cpp @@ -60,34 +60,3 @@ TEST_CASE("make sure json_mods works", "[components]") { auto boss2 = world.get(devils_fingers); REQUIRE(boss2.stage != std::nullopt); } - - -TEST_CASE("animation component special cases", "[components]") { - Animation anim; - - anim.easing = ease::NONE; - float res = anim.twitching(); - REQUIRE(res == 0.0); - - anim.easing = ease::SINE; - anim.subframe = 1.0f; - res = anim.twitching(); - REQUIRE(!std::isnan(res)); - - anim.easing = ease::OUT_CIRC; - res = anim.twitching(); - REQUIRE(!std::isnan(res)); - - anim.easing = ease::OUT_BOUNCE; - res = anim.twitching(); - REQUIRE(!std::isnan(res)); - - anim.easing = ease::IN_OUT_BACK; - res = anim.twitching(); - REQUIRE(!std::isnan(res)); - - anim.easing = ease::FUCKFACE; - bool throws = false; - try { anim.twitching(); } catch(...) { throws = true; } - REQUIRE(throws); -}