First idea for the new animation api.

This commit is contained in:
Zed A. Shaw 2026-01-17 00:08:08 -05:00
parent acadf3ca31
commit 7d08e60537

View file

@ -10,80 +10,99 @@ using namespace components;
using namespace textures; using namespace textures;
struct Sheet { struct Sheet {
std::string texture_name; int width{0};
int width; int height{0};
int height; int frame_width{0};
int frame_width; int frame_height{0};
int frame_height;
}; };
struct Sequence { struct Sequence {
size_t current_frame; std::vector<size_t> frames{};
float subframe; std::vector<float> durations{};
std::vector<size_t> frames; size_t current{0};
std::vector<float> durations; int loop_count{0};
size_t frame_count{frames.size()};
sf::Clock timer{};
}; };
struct Transform { struct Transform {
// how to know when a transform ends? // how to know when a transform ends?
float min_x = 1.0f; float min_x{1.0f};
float min_y = 1.0f; float min_y{1.0f};
float max_x = 1.0f; float max_x{1.0f};
float max_y = 1.0f; float max_y{1.0f};
bool simple = true; bool simple{true};
bool flipped = false; bool flipped{false};
float ease_rate = 0.5f; float ease_rate{0.5f};
bool scaled = false; bool scaled{false};
bool stationary = false; bool stationary{false};
// these can handled by the onLoop, same as ganim8 does it // these can handled by the onLoop, same as ganim8 does it
bool toggled = false; bool toggled{false};
bool looped = false; bool looped{false};
std::shared_ptr<sf::Shader> shader = nullptr; std::shared_ptr<sf::Shader> shader{nullptr};
// change to using a callback function for these // change to using a callback function for these
ease::Style easing = ease::IN_OUT_BACK; ease::Style easing = ease::IN_OUT_BACK;
ease::Motion motion = ease::RUSH; ease::Motion motion = ease::RUSH;
}; };
/* Gets the number of times it looped, and returns if it should stop. */ /* Gets the number of times it looped, and returns if it should stop. */
using OnLoopHandler = std::function<bool(int)>; using OnLoopHandler = std::function<bool(Sequence& seq, Transform& tr)>;
bool DefaultOnLoop(Sequence& seq, Transform& tr) {
seq.current = 0;
return tr.looped;
}
class Animate2 { class Animate2 {
public: public:
Sheet& $sheet; Sheet $sheet;
Sequence $sequence; Sequence $sequence;
Transform $transform; Transform $transform;
std::shared_ptr<sf::Sprite> $sprite = nullptr; OnLoopHandler onLoop = DefaultOnLoop;
std::shared_ptr<sf::Texture> $texture = nullptr; bool playing = false;
Animate2(Sheet sheet, Sequence seq, Transform trans) : void play() {
$sheet(sheet), $sequence(seq), $transform(trans) dbc::check(!playing, "can't call play while playing?");
{ $sequence.current = 0;
auto st = textures::get_sprite($sheet.texture_name); $sequence.loop_count = 0;
$sprite = st.sprite; playing = true;
$texture = st.texture; $sequence.timer.start();
} }
OnLoopHandler onLoop = nullptr; 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() {
/*
* 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();
Sheet sheet{ Sheet sheet{
.texture_name{"rat_king_boss"},
.width{720*2}, .width{720*2},
.height{720}, .height{720},
.frame_width{720}, .frame_width{720},
@ -91,12 +110,13 @@ TEST_CASE("new animation system", "[animation-new]") {
}; };
Sequence sequence{ Sequence sequence{
.current_frame{0},
.subframe{0.0f},
.frames{0,1}, .frames{0,1},
.durations{0.1f,0.1f}, .durations{0.01f,0.03f}
}; };
REQUIRE(sequence.frame_count == sequence.frames.size());
REQUIRE(sequence.frame_count == sequence.durations.size());
Transform transform{ Transform transform{
.min_x{1.0f}, .min_x{1.0f},
.min_y{1.0f}, .min_y{1.0f},
@ -111,13 +131,71 @@ TEST_CASE("new animation system", "[animation-new]") {
.looped{false}, .looped{false},
}; };
Animate2 anim{sheet, sequence, transform}; return {sheet, sequence, transform};
anim.onLoop = [](int loop_count) -> bool { }
return loop_count < 2;
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("animation easing tests", "[animation]") { TEST_CASE("animation easing tests", "[animation]") {
Animation anim; Animation anim;