Sorted out the animation vs. easing contradiction I believe. Now there's a separate easing_duration that's calculated from the total duration of all frames. Then a easing_position determines where in the total the animation is, which is fed to the asing functions as a ration of easing_position / easing_duration.

This commit is contained in:
Zed A. Shaw 2026-02-21 01:57:33 -05:00
parent 0c798c9e0d
commit 1baca783fc
9 changed files with 47 additions and 75 deletions

View file

@ -10,6 +10,8 @@
constexpr float SUB_FRAME_SENSITIVITY = 0.999f; constexpr float SUB_FRAME_SENSITIVITY = 0.999f;
namespace animate2 { namespace animate2 {
using namespace std::chrono_literals;
std::vector<sf::IntRect> Animate2::calc_frames() { std::vector<sf::IntRect> Animate2::calc_frames() {
dbc::check(sequence.frames.size() == sequence.durations.size(), "sequence.frames.size() != sequence.durations.size()"); dbc::check(sequence.frames.size() == sequence.durations.size(), "sequence.frames.size() != sequence.durations.size()");
@ -94,30 +96,28 @@ namespace animate2 {
* elapsed is DELTA, or use elapsed here? * elapsed is DELTA, or use elapsed here?
*/ */
void Animate2::update() { void Animate2::update() {
dbc::check(sequence.easing_duration > 0.0, "bad easing duration");
dbc::check(playing, "attempt to update animation that's not playing"); 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()"); dbc::check(sequence.frame_count == sequence.frames.size(), "frame_count doesn't match frame.size()");
auto duration = sequence.durations.at(sequence.current); auto [ticks, alpha] = sequence.timer.commit();
auto elapsed = sequence.timer.getElapsedTime().toDuration(); int duration = sequence.durations.at(sequence.current);
// std::cout << "subframe: " << sequence.subframe << " elapsed: " << elapsed << " duration: " << duration << std::endl; sequence.subframe += ticks;
sequence.easing_position += ticks;
bool frame_change = false; bool frame_change = false;
if(elapsed < duration) { if(sequence.subframe >= duration) {
// BUG: subframe will just run crazy because I don't actually do delta time difference here
sequence.subframe = std::lerp(sequence.subframe, 1.0, sequence.timer.DELTA * transform.ease_rate);
} else {
sequence.timer.restart(); sequence.timer.restart();
sequence.current++; sequence.current++;
sequence.subframe = 0;
if(sequence.subframe > SUB_FRAME_SENSITIVITY) {
sequence.subframe = 0.0f;
}
frame_change = true; frame_change = true;
} }
if(sequence.current >= sequence.frame_count) { if(sequence.current >= sequence.frame_count) {
sequence.loop_count++; sequence.loop_count++;
sequence.easing_position = 0;
playing = onLoop(sequence, transform); playing = onLoop(sequence, transform);
} }
@ -142,20 +142,18 @@ namespace animate2 {
} }
} }
std::pair<int, double> Animate2::commit() {
return sequence.timer.commit();
}
void Timer::start() { void Timer::start() {
clock.start(); clock.start();
prev_time = clock.getElapsedTime().asSeconds(); prev_time = clock.getElapsedTime().asSeconds();
} }
void Timer::reset() { void Timer::reset() {
elapsed_ticks = 0;
clock.reset(); clock.reset();
} }
void Timer::restart() { void Timer::restart() {
elapsed_ticks = 0;
clock.restart(); clock.restart();
prev_time = clock.getElapsedTime().asSeconds(); prev_time = clock.getElapsedTime().asSeconds();
} }
@ -182,6 +180,8 @@ namespace animate2 {
accumulator -= tick_count * DELTA; accumulator -= tick_count * DELTA;
// that leaves the remaining errors for next loop // that leaves the remaining errors for next loop
elapsed_ticks += tick_count;
// alpha is then what we lerp...but WHY?! // alpha is then what we lerp...but WHY?!
alpha = accumulator / DELTA; alpha = accumulator / DELTA;
@ -190,14 +190,9 @@ namespace animate2 {
} }
void Transform::apply(Sequence& seq, sf::Vector2f& pos_out, sf::Vector2f& scale_out) { void Transform::apply(Sequence& seq, sf::Vector2f& pos_out, sf::Vector2f& scale_out) {
// float dt = 1 - std::powf(ease_rate, seq.subframe + 0.0001); float tick = easing_func(seq.easing_position / seq.easing_duration);
float tick = easing_func(seq.subframe);
motion_func(*this, pos_out, scale_out, tick, relative); motion_func(*this, pos_out, scale_out, tick, relative);
// fmt::println("sub: {}, tick: {}, tr: {},{}; pos: {},{}; scale: {},{}",
// seq.subframe, tick, min_y, max_y, pos_out.x, pos_out.y,
// scale_out.x, scale_out.y);
} }
bool Animate2::has_form(const std::string& as_form) { bool Animate2::has_form(const std::string& as_form) {
@ -224,6 +219,12 @@ namespace animate2 {
sequence = sequences.at(seq_name); sequence = sequences.at(seq_name);
transform = transforms.at(tr_name); transform = transforms.at(tr_name);
// BUG: should this be configurable instead?
for(auto duration : sequence.durations) {
sequence.easing_duration += float(duration);
}
dbc::check(sequence.easing_duration > 0.0, "bad easing duration");
$frame_rects = calc_frames(); $frame_rects = calc_frames();
transform.easing_func = ease2::get_easing(transform.easing); transform.easing_func = ease2::get_easing(transform.easing);
transform.motion_func = ease2::get_motion(transform.motion); transform.motion_func = ease2::get_motion(transform.motion);

View file

@ -29,6 +29,7 @@ namespace animate2 {
double current_time = 0.0; double current_time = 0.0;
double frame_duration = 0.0; double frame_duration = 0.0;
double alpha = 0.0; double alpha = 0.0;
int elapsed_ticks = 0;
sf::Clock clock{}; sf::Clock clock{};
std::pair<int, double> commit(); std::pair<int, double> commit();
@ -40,12 +41,14 @@ namespace animate2 {
struct Sequence { struct Sequence {
std::vector<int> frames{}; std::vector<int> frames{};
std::vector<std::chrono::milliseconds> durations{}; std::vector<int> durations{}; // in ticks
size_t current{0}; size_t current{0};
int loop_count{0}; int loop_count{0};
size_t frame_count{0}; size_t frame_count{0};
Timer timer{}; Timer timer{};
float subframe{0.0f}; int subframe{0};
float easing_duration{0.0f};
float easing_position{0.0f};
}; };
struct Transform { struct Transform {
@ -55,7 +58,6 @@ namespace animate2 {
float max_x{1.0f}; float max_x{1.0f};
float max_y{1.0f}; float max_y{1.0f};
bool flipped{false}; bool flipped{false};
float ease_rate{0.5f};
bool scaled{false}; bool scaled{false};
bool relative{false}; bool relative{false};
@ -123,7 +125,6 @@ namespace animate2 {
void update(); void update();
void motion(sf::Transformable& sprite, sf::Vector2f pos, sf::Vector2f scale); void motion(sf::Transformable& sprite, sf::Vector2f pos, sf::Vector2f scale);
void motion(sf::View& view_out, sf::Vector2f pos, sf::Vector2f scale); void motion(sf::View& view_out, sf::Vector2f pos, sf::Vector2f scale);
std::pair<int, double> commit();
}; };
Animate2 load(const std::string &file, const std::string &anim_name); Animate2 load(const std::string &file, const std::string &anim_name);
@ -131,6 +132,6 @@ namespace animate2 {
ENROLL_COMPONENT(Sheet, frames, frame_width, frame_height); ENROLL_COMPONENT(Sheet, frames, frame_width, frame_height);
ENROLL_COMPONENT(Sequence, frames, durations); ENROLL_COMPONENT(Sequence, frames, durations);
ENROLL_COMPONENT(Transform, min_x, min_y, max_x, max_y, ENROLL_COMPONENT(Transform, min_x, min_y, max_x, max_y,
flipped, ease_rate, scaled, relative, toggled, looped, easing, motion); flipped, scaled, relative, toggled, looped, easing, motion);
ENROLL_COMPONENT(Animate2, sheet, sequences, transforms, forms, sounds); ENROLL_COMPONENT(Animate2, sheet, sequences, transforms, forms, sounds);
} }

View file

@ -6,9 +6,9 @@
"frame_height": 720 "frame_height": 720
}, },
"sequences": { "sequences": {
"idle": {"frames": [0], "durations": [800] }, "idle": {"frames": [0], "durations": [47] },
"hurt": {"frames": [1, 1], "durations": [800, 240] }, "hurt": {"frames": [0, 1], "durations": [5, 57] },
"attack": {"frames": [1, 0], "durations": [800, 200] } "attack": {"frames": [0, 1, 0, 1], "durations": [35, 15, 5, 5] }
}, },
"transforms": { "transforms": {
"rushing": { "rushing": {
@ -17,7 +17,6 @@
"max_x": 0.8, "max_x": 0.8,
"max_y": 0.8, "max_y": 0.8,
"flipped": false, "flipped": false,
"ease_rate": 5.0,
"scaled": true, "scaled": true,
"toggled": false, "toggled": false,
"looped": true, "looped": true,
@ -31,7 +30,6 @@
"max_x": 20.0, "max_x": 20.0,
"max_y": 20.0, "max_y": 20.0,
"flipped": true, "flipped": true,
"ease_rate": 5.0,
"scaled": true, "scaled": true,
"toggled": false, "toggled": false,
"looped": false, "looped": false,
@ -45,7 +43,6 @@
"max_x": 0, "max_x": 0,
"max_y": -10, "max_y": -10,
"flipped": false, "flipped": false,
"ease_rate": 3.0,
"scaled": false, "scaled": false,
"toggled": false, "toggled": false,
"looped": true, "looped": true,
@ -72,9 +69,9 @@
"frame_height": 540 "frame_height": 540
}, },
"sequences": { "sequences": {
"idle": {"frames": [0], "durations": [800] }, "idle": {"frames": [0], "durations": [47] },
"hurt": {"frames": [0], "durations": [800] }, "hurt": {"frames": [0], "durations": [47] },
"attack": {"frames": [0], "durations": [800] } "attack": {"frames": [0], "durations": [47] }
}, },
"transforms": { "transforms": {
"breathe": { "breathe": {
@ -83,7 +80,6 @@
"max_x": 0, "max_x": 0,
"max_y": -10, "max_y": -10,
"flipped": false, "flipped": false,
"ease_rate": 3.0,
"scaled": false, "scaled": false,
"toggled": false, "toggled": false,
"looped": true, "looped": true,
@ -110,7 +106,7 @@
"frame_height": 256 "frame_height": 256
}, },
"sequences": { "sequences": {
"idle": {"frames": [0,1,2], "durations": [100, 100, 100] } "idle": {"frames": [0,1,2], "durations": [10, 10, 10] }
}, },
"transforms": { "transforms": {
"render": { "render": {
@ -119,7 +115,6 @@
"max_x": 0, "max_x": 0,
"max_y": 0, "max_y": 0,
"flipped": false, "flipped": false,
"ease_rate": 3.0,
"scaled": true, "scaled": true,
"toggled": false, "toggled": false,
"looped": true, "looped": true,

View file

@ -6,11 +6,11 @@
"frame_height": 768 "frame_height": 768
}, },
"sequences": { "sequences": {
"idle": {"frames": [0], "durations": [800] }, "idle": {"frames": [0], "durations": [60] },
"pan": {"frames": [0], "durations": [800] }, "pan": {"frames": [0], "durations": [60] },
"shake": {"frames": [0], "durations": [800] }, "shake": {"frames": [0], "durations": [60] },
"dolly": {"frames": [0], "durations": [800] }, "dolly": {"frames": [0], "durations": [60] },
"bounce": {"frames": [0], "durations": [800] } "bounce": {"frames": [0], "durations": [60] }
}, },
"transforms": { "transforms": {
"pan": { "pan": {
@ -19,7 +19,6 @@
"max_x": 0.0, "max_x": 0.0,
"max_y": 0.0, "max_y": 0.0,
"flipped": false, "flipped": false,
"ease_rate": 5.0,
"scaled": false, "scaled": false,
"toggled": false, "toggled": false,
"looped": true, "looped": true,
@ -33,7 +32,6 @@
"max_x": 10.0, "max_x": 10.0,
"max_y": 10.0, "max_y": 10.0,
"flipped": false, "flipped": false,
"ease_rate": 5.0,
"scaled": false, "scaled": false,
"toggled": false, "toggled": false,
"looped": true, "looped": true,
@ -47,7 +45,6 @@
"max_x": 1.0, "max_x": 1.0,
"max_y": 1.0, "max_y": 1.0,
"flipped": false, "flipped": false,
"ease_rate": 3.0,
"scaled": false, "scaled": false,
"toggled": false, "toggled": false,
"looped": true, "looped": true,
@ -61,7 +58,6 @@
"max_x": 0, "max_x": 0,
"max_y": 0, "max_y": 0,
"flipped": false, "flipped": false,
"ease_rate": 3.0,
"scaled": false, "scaled": false,
"toggled": false, "toggled": false,
"looped": true, "looped": true,

View file

@ -98,8 +98,6 @@ namespace cinematic {
} }
void Camera::update() { void Camera::update() {
// REFACTOR: there's no connection between anim.commit() and anim.update()
auto [ticks, alpha] = anim.commit();
if(anim.playing) anim.update(); if(anim.playing) anim.update();
} }

View file

@ -131,7 +131,6 @@ namespace scene {
inline void this_is_stupid_refactor(std::vector<Element>& elements) { inline void this_is_stupid_refactor(std::vector<Element>& elements) {
for(auto& element : elements) { for(auto& element : elements) {
if(element.anim.playing) { if(element.anim.playing) {
auto [ticks, alpha] = element.anim.commit();
element.anim.update(); element.anim.update();
element.anim.motion(*element.st.sprite, element.pos, element.scale); element.anim.motion(*element.st.sprite, element.pos, element.scale);
element.anim.apply(*element.st.sprite); element.anim.apply(*element.st.sprite);

View file

@ -22,7 +22,7 @@ Animate2 load_animation(const string& name) {
anim.set_form("attack"); anim.set_form("attack");
anim.transform.looped = false; anim.transform.looped = false;
anim.sequence.durations = {Random::milliseconds(5, 33), Random::milliseconds(5, 33)}; anim.sequence.durations = {Random::uniform(1, 5), Random::uniform(1, 5)};
return anim; return anim;
} }

View file

@ -54,20 +54,11 @@ namespace animator {
} }
void FSM::START(Event ev) { void FSM::START(Event ev) {
switch(ev) { state(State::ANIMATE);
case Event::TICK:
state(State::ANIMATE);
break;
default:
state(State::START);
}
} }
void FSM::ANIMATE(Event ev) { void FSM::ANIMATE(Event ev) {
switch(ev) { switch(ev) {
case Event::TICK:
run_animation();
break;
case Event::PLAY_STOP: case Event::PLAY_STOP:
if($anim.playing) { if($anim.playing) {
$anim.stop(); $anim.stop();
@ -122,7 +113,7 @@ namespace animator {
$anim.play(); $anim.play();
} }
void FSM::run_animation() { void FSM::update() {
if($anim.playing) { if($anim.playing) {
$anim.update(); $anim.update();
$anim.apply(*$ui.sprite); $anim.apply(*$ui.sprite);
@ -131,14 +122,6 @@ namespace animator {
} }
} }
void FSM::tick() {
auto [ticks, alpha] = $anim.commit();
for(int i = 0; i < ticks; i++) {
event(animator::Event::TICK, {});
}
}
void FSM::check_update() { void FSM::check_update() {
if($timer.getElapsedTime().toDuration() > 500ms) { if($timer.getElapsedTime().toDuration() > 500ms) {
try { try {
@ -356,9 +339,9 @@ int main(int argc, char* argv[]) {
main.init(sprite_name, anim_name, background); main.init(sprite_name, anim_name, background);
while(main.active()) { while(main.active()) {
main.tick(); main.update();
main.check_update();
main.render(); main.render();
main.check_update();
main.handle_keyboard_mouse(); main.handle_keyboard_mouse();
} }

View file

@ -66,8 +66,7 @@ namespace animator {
void handle_keyboard_mouse(); void handle_keyboard_mouse();
void render(); void render();
bool active(); bool active();
void run_animation(); void update();
void tick();
void reload(); void reload();
void check_update(); void check_update();
void change_form(int direction); void change_form(int direction);