#include #include "animation.hpp" #include "textures.hpp" #include "dinkyecs.hpp" #include "config.hpp" #include #include #include #include #include "rand.hpp" #include "animate2.hpp" using namespace components; using namespace textures; using namespace std::chrono_literals; using namespace animate2; void FAKE_RENDER() { std::this_thread::sleep_for(Random::milliseconds(5, 32)); } void PLAY_TEST(Animate2 &anim) { anim.play(); while(anim.playing) { anim.update(); FAKE_RENDER(); } 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 = animate2::load("assets/animate2.json", "rat_king_boss"); anim.sequence.durations = {Random::milliseconds(5, 33), Random::milliseconds(5, 33)}; 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("confirm frame sequencing works", "[animation-new]") { textures::init(); animation::init(); auto anim = animate2::load("assets/animate2.json", "rat_king_boss"); auto boss = textures::get_sprite("rat_king_boss"); sf::IntRect init_rect{{0,0}, {anim.sheet.frame_width, anim.sheet.frame_height}}; anim.play(); bool loop_ran = false; // this will check that it moved to the next frame anim.onLoop = [&](auto& seq, auto& tr) -> bool { seq.current = 0; loop_ran = true; REQUIRE(boss.sprite->getTextureRect() != init_rect); return false; }; anim.onFrame = [&](){ anim.apply(*boss.sprite); }; while(anim.playing) { anim.update(); FAKE_RENDER(); } REQUIRE(loop_ran == true); REQUIRE(anim.playing == false); // this confirms it went back to the first frame REQUIRE(boss.sprite->getTextureRect() == init_rect); } TEST_CASE("confirm transition changes work", "[animation-new]") { textures::init(); animation::init(); auto sprite = *textures::get_sprite("rat_king_boss").sprite; sf::Vector2f pos{100,100}; sprite.setPosition(pos); auto scale = sprite.getScale(); auto anim = animate2::load("assets/animate2.json", "rat_king_boss"); // also testing that onFrame being null means it's not run REQUIRE(anim.onFrame == nullptr); anim.play(); REQUIRE(anim.playing == true); while(anim.playing) { anim.update(); anim.motion(sprite, pos, scale); FAKE_RENDER(); } REQUIRE(anim.playing == false); REQUIRE(pos == sf::Vector2f{100, 100}); REQUIRE(scale != sf::Vector2f{0,0}); } TEST_CASE("playing with delta time", "[animation-new]") { animate2::Timer timer; timer.start(); for(int i = 0; i < 20; i++) { FAKE_RENDER(); auto [tick_count, alpha] = timer.commit(); // fmt::println("tick: {}, alpha: {}", tick_count, alpha); } }