#include #include "animation.hpp" #include "textures.hpp" #include "dinkyecs.hpp" #include "config.hpp" #include #include using namespace components; using namespace textures; struct Sheet { int width{0}; int height{0}; int frame_width{0}; int frame_height{0}; }; struct Sequence { std::vector frames{}; std::vector durations{}; size_t current{0}; int loop_count{0}; size_t frame_count{frames.size()}; sf::Clock timer{}; }; struct Transform { // how to know when a transform ends? float min_x{1.0f}; float min_y{1.0f}; float max_x{1.0f}; float max_y{1.0f}; bool simple{true}; bool flipped{false}; float ease_rate{0.5f}; bool scaled{false}; bool stationary{false}; // these can handled by the onLoop, same as ganim8 does it bool toggled{false}; bool looped{false}; std::shared_ptr shader{nullptr}; // change to using a callback function for these ease::Style easing = ease::IN_OUT_BACK; ease::Motion motion = ease::RUSH; }; /* Gets the number of times it looped, and returns if it should stop. */ using OnLoopHandler = std::function; bool DefaultOnLoop(Sequence& seq, Transform& tr) { seq.current = 0; return tr.looped; } class Animate2 { public: Sheet $sheet; Sequence $sequence; Transform $transform; OnLoopHandler onLoop = DefaultOnLoop; bool playing = false; void play() { dbc::check(!playing, "can't call play while playing?"); $sequence.current = 0; $sequence.loop_count = 0; playing = true; $sequence.timer.start(); } void stop() { playing = false; $sequence.timer.reset(); } // need one for each kind of thing to animate void apply(sf::Sprite& sprite, sf::Vector2f pos) { } // replaces step void update() { dbc::check(playing, "attempt to update animation that's not playing"); dbc::check($sequence.frame_count == $sequence.frames.size(), "frame_count doesn't match frame.size()"); auto secs = $sequence.timer.getElapsedTime().asSeconds(); auto duration = $sequence.durations[$sequence.current]; if(secs >= duration) { $sequence.timer.restart(); $sequence.current++; } if($sequence.current >= $sequence.frame_count) { $sequence.loop_count++; playing = onLoop($sequence, $transform); } dbc::check($sequence.current < $sequence.frame_count, "current frame out of frames.size()"); } }; Animate2 crafter() { Sheet sheet{ .width{720*2}, .height{720}, .frame_width{720}, .frame_height{720}, }; Sequence sequence{ .frames{0,1}, .durations{0.01f,0.03f} }; REQUIRE(sequence.frame_count == sequence.frames.size()); REQUIRE(sequence.frame_count == sequence.durations.size()); Transform transform{ .min_x{1.0f}, .min_y{1.0f}, .max_x{1.0f}, .max_y{1.0f}, .simple{true}, .flipped{false}, .ease_rate{0.5f}, .scaled{false}, .stationary{false}, .toggled{false}, .looped{false}, }; return {sheet, sequence, transform}; } void PLAY_TEST(Animate2 &anim) { anim.play(); while(anim.playing) { anim.update(); } REQUIRE(anim.playing == false); } /* * Animation is a Sheet + Sequence + Transform. * * A Sheet is just a grid of images with a predefined size for each cell. Arbitrary sized cells not supported. * * A Sequence is a list of Sheet cells _in any order_. See https://github.com/yottahmd/ganim8-lib. Sequences have a timing element for the cells, possibly a list of durations or a single duration. * * A Transform is combinations of scale and/or position easing/motion, shader effects, and things like if it's looped or toggled. * * I like the ganim8 onLoop concept, just a callback that says what to do when the animation has looped. */ TEST_CASE("new animation system", "[animation-new]") { textures::init(); auto anim = crafter(); PLAY_TEST(anim); bool onLoop_ran = false; anim.onLoop = [&](auto& seq, auto& tr) -> bool { seq.current = 0; onLoop_ran = true; return tr.looped; }; PLAY_TEST(anim); REQUIRE(onLoop_ran == true); // only runs twice anim.onLoop = [](auto& seq, auto& tr) -> bool { if(seq.loop_count == 2) { seq.current = 0; return false; } else { seq.current = seq.current % seq.frame_count; return true; } }; PLAY_TEST(anim); REQUIRE(anim.$sequence.loop_count == 2); // stops at end anim.onLoop = [](auto& seq, auto& tr) -> bool { if(seq.loop_count == 1) { seq.current = seq.frame_count - 1; return false; } else { return true; } }; } TEST_CASE("easing functions in animate2", "[animation-new]") { auto anim = crafter(); } 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)); } TEST_CASE("animation utility API", "[animation]") { textures::init(); animation::init(); auto blanket = textures::get_sprite("ritual_crafting_area"); auto anim = animation::load("ritual_crafting_area"); anim.play(); while(anim.apply(*blanket.sprite, {0,0})) { fmt::println("animation: {}", anim.subframe); } }