Refactor the animate2 and then start working on the motion feature.
This commit is contained in:
parent
992b8f0e0a
commit
60c405b1fc
9 changed files with 408 additions and 290 deletions
146
animate2.cpp
Normal file
146
animate2.cpp
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
#include "animate2.hpp"
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
#include "dbc.hpp"
|
||||||
|
#include "rand.hpp"
|
||||||
|
|
||||||
|
namespace animate2 {
|
||||||
|
std::vector<sf::IntRect> Animate2::calc_frames() {
|
||||||
|
std::vector<sf::IntRect> frames;
|
||||||
|
|
||||||
|
for(int frame_i : $sequence.frames) {
|
||||||
|
frames.emplace_back(
|
||||||
|
sf::Vector2i{$sheet.frame_width * frame_i, 0}, // NOTE: one row only for now
|
||||||
|
sf::Vector2i{$sheet.frame_width,
|
||||||
|
$sheet.frame_height});
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animate2::play() {
|
||||||
|
dbc::check(!playing, "can't call play while playing?");
|
||||||
|
$sequence.current = 0;
|
||||||
|
$sequence.loop_count = 0;
|
||||||
|
playing = true;
|
||||||
|
$sequence.timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animate2::stop() {
|
||||||
|
playing = false;
|
||||||
|
$sequence.timer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// need one for each kind of thing to animate
|
||||||
|
// NOTE: possibly find a way to only run apply on frame change?
|
||||||
|
void Animate2::apply(sf::Sprite& sprite) {
|
||||||
|
dbc::check($sequence.current < $frame_rects.size(), "current frame past $frame_rects");
|
||||||
|
// NOTE: pos is not updated yet
|
||||||
|
auto& rect = $frame_rects.at($sequence.current);
|
||||||
|
sprite.setTextureRect(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaces step
|
||||||
|
void Animate2::update_frame() {
|
||||||
|
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 duration = $sequence.durations.at($sequence.current);
|
||||||
|
bool frame_change = false;
|
||||||
|
|
||||||
|
if($sequence.timer.getElapsedTime() >= duration) {
|
||||||
|
$sequence.timer.restart();
|
||||||
|
$sequence.current++;
|
||||||
|
frame_change = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($sequence.current >= $sequence.frame_count) {
|
||||||
|
$sequence.loop_count++;
|
||||||
|
playing = onLoop($sequence, $transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(frame_change && onFrame != nullptr) onFrame();
|
||||||
|
|
||||||
|
dbc::check($sequence.current < $sequence.frame_count, "onLoop fail: current frame out of frames.size()");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animate2::update() {
|
||||||
|
update_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animate2::motion(sf::Vector2f& pos, sf::Vector2f& scale) {
|
||||||
|
$transform.twitching($sequence);
|
||||||
|
$transform.lerp($sequence, pos, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Transform::twitching(Sequence& seq) {
|
||||||
|
float subframe = seq.timer.getElapsedTime().asSeconds();
|
||||||
|
float tick = 1 - std::powf(ease_rate, subframe + 0.0001);
|
||||||
|
fmt::println("TICK {}; subframe: {}", tick, subframe);
|
||||||
|
|
||||||
|
switch(easing) {
|
||||||
|
case ease::NONE:
|
||||||
|
return 0.0;
|
||||||
|
case ease::SINE:
|
||||||
|
return std::abs(std::sin(subframe * ease_rate));
|
||||||
|
case ease::OUT_CIRC:
|
||||||
|
return ease::out_circ(tick);
|
||||||
|
case ease::OUT_BOUNCE:
|
||||||
|
return ease::out_bounce(tick);
|
||||||
|
case ease::IN_OUT_BACK:
|
||||||
|
return ease::in_out_back(tick);
|
||||||
|
case ease::RANDOM:
|
||||||
|
return Random::uniform_real(0.0001f, 1.0f);
|
||||||
|
case ease::NORM_DIST:
|
||||||
|
return Random::normal(0.5f, 0.1f);
|
||||||
|
case ease::LINEAR:
|
||||||
|
return tick;
|
||||||
|
default:
|
||||||
|
dbc::sentinel(
|
||||||
|
fmt::format("Invalid easing {} given to animation",
|
||||||
|
int(easing)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transform::lerp(Sequence& seq, sf::Vector2f& scale_out, sf::Vector2f& pos_out) {
|
||||||
|
float tick = twitching(seq);
|
||||||
|
|
||||||
|
if(stationary) {
|
||||||
|
switch(motion) {
|
||||||
|
case ease::SHAKE: {
|
||||||
|
pos_out.x += std::lerp(min_x, max_x, tick);
|
||||||
|
} break;
|
||||||
|
case ease::BOUNCE: {
|
||||||
|
pos_out.y -= std::lerp(min_y, max_y, tick);
|
||||||
|
} break;
|
||||||
|
case ease::RUSH: {
|
||||||
|
scale_out.x = std::lerp(min_x, max_x, tick);
|
||||||
|
scale_out.y = std::lerp(min_y, max_y, tick);
|
||||||
|
pos_out.y = pos_out.y - (pos_out.y * scale_out.y - pos_out.y);
|
||||||
|
} break;
|
||||||
|
case ease::SQUEEZE: {
|
||||||
|
scale_out.x *= std::lerp(min_x, max_x, tick);
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case ease::SQUASH: {
|
||||||
|
scale_out.y *= std::lerp(min_y, max_y, tick);
|
||||||
|
} break;
|
||||||
|
case ease::STRETCH: {
|
||||||
|
scale_out.x = std::lerp(min_x, max_x, tick);
|
||||||
|
} break;
|
||||||
|
case ease::GROW: {
|
||||||
|
scale_out.y = std::lerp(min_y, max_y, tick);
|
||||||
|
} break;
|
||||||
|
case ease::SLIDE: {
|
||||||
|
pos_out.x = std::lerp(min_x, max_x, tick);
|
||||||
|
pos_out.y = std::lerp(min_y, max_y, tick);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
dbc::sentinel("Unknown animation.motion setting.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scale_out.x = std::lerp(scale_out.x * min_x, scale_out.x * max_x, tick);
|
||||||
|
scale_out.y = std::lerp(scale_out.y * min_y, scale_out.y * max_y, tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
animate2.hpp
Normal file
81
animate2.hpp
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
#include "easings.hpp"
|
||||||
|
#include <SFML/Graphics/Rect.hpp>
|
||||||
|
#include <SFML/Graphics/Sprite.hpp>
|
||||||
|
#include <SFML/System/Clock.hpp>
|
||||||
|
#include <SFML/System/Time.hpp>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace animate2 {
|
||||||
|
struct Sheet {
|
||||||
|
int width{0};
|
||||||
|
int height{0};
|
||||||
|
int frame_width{0};
|
||||||
|
int frame_height{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Sequence {
|
||||||
|
std::vector<int> frames{};
|
||||||
|
std::vector<std::chrono::milliseconds> 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<sf::Shader> shader{nullptr};
|
||||||
|
// change to using a callback function for these
|
||||||
|
ease::Style easing = ease::IN_OUT_BACK;
|
||||||
|
ease::Motion motion = ease::RUSH;
|
||||||
|
|
||||||
|
float twitching(Sequence& seq);
|
||||||
|
void lerp(Sequence& seq, sf::Vector2f& scale_out, sf::Vector2f& pos_out);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Gets the number of times it looped, and returns if it should stop. */
|
||||||
|
using OnLoopHandler = std::function<bool(Sequence& seq, Transform& tr)>;
|
||||||
|
using OnFrameHandler = std::function<void()>;
|
||||||
|
|
||||||
|
inline bool DefaultOnLoop(Sequence& seq, Transform& tr) {
|
||||||
|
seq.current = 0;
|
||||||
|
return tr.looped;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Animate2 {
|
||||||
|
public:
|
||||||
|
Sheet $sheet;
|
||||||
|
Sequence $sequence;
|
||||||
|
Transform $transform;
|
||||||
|
OnFrameHandler onFrame = nullptr;
|
||||||
|
|
||||||
|
std::vector<sf::IntRect> $frame_rects{calc_frames()};
|
||||||
|
OnLoopHandler onLoop = DefaultOnLoop;
|
||||||
|
bool playing = false;
|
||||||
|
|
||||||
|
std::vector<sf::IntRect> calc_frames();
|
||||||
|
void play();
|
||||||
|
void stop();
|
||||||
|
void apply(sf::Sprite& sprite);
|
||||||
|
void update_frame();
|
||||||
|
void update();
|
||||||
|
void motion(sf::Vector2f& pos, sf::Vector2f& scale);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "animation.hpp"
|
#include "animation.hpp"
|
||||||
#include "rand.hpp"
|
#include "rand.hpp"
|
||||||
|
#include "textures.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace components {
|
namespace components {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "components.hpp"
|
#include "components.hpp"
|
||||||
#include "textures.hpp"
|
|
||||||
#include "easings.hpp"
|
#include "easings.hpp"
|
||||||
#include "dinkyecs.hpp"
|
#include "dinkyecs.hpp"
|
||||||
#include <SFML/Graphics/Rect.hpp>
|
#include <SFML/Graphics/Rect.hpp>
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ dependencies += [
|
||||||
sources = [
|
sources = [
|
||||||
'ai.cpp',
|
'ai.cpp',
|
||||||
'ai_debug.cpp',
|
'ai_debug.cpp',
|
||||||
|
'animate2.cpp',
|
||||||
'animation.cpp',
|
'animation.cpp',
|
||||||
'animation.cpp',
|
'animation.cpp',
|
||||||
'autowalker.cpp',
|
'autowalker.cpp',
|
||||||
|
|
@ -137,6 +138,7 @@ sources = [
|
||||||
|
|
||||||
executable('runtests', sources + [
|
executable('runtests', sources + [
|
||||||
'tests/ai.cpp',
|
'tests/ai.cpp',
|
||||||
|
'tests/animate2.cpp',
|
||||||
'tests/animation.cpp',
|
'tests/animation.cpp',
|
||||||
'tests/base.cpp',
|
'tests/base.cpp',
|
||||||
'tests/battle.cpp',
|
'tests/battle.cpp',
|
||||||
|
|
|
||||||
174
tests/animate2.cpp
Normal file
174
tests/animate2.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include "animation.hpp"
|
||||||
|
#include "textures.hpp"
|
||||||
|
#include "dinkyecs.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include "rand.hpp"
|
||||||
|
#include "animate2.hpp"
|
||||||
|
|
||||||
|
using namespace components;
|
||||||
|
using namespace textures;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using namespace animate2;
|
||||||
|
|
||||||
|
Animate2 crafter() {
|
||||||
|
Sheet sheet{
|
||||||
|
.width{720*2},
|
||||||
|
.height{720},
|
||||||
|
.frame_width{720},
|
||||||
|
.frame_height{720},
|
||||||
|
};
|
||||||
|
|
||||||
|
Sequence sequence{
|
||||||
|
.frames{0,1},
|
||||||
|
.durations{Random::milliseconds(1, 100), Random::milliseconds(1, 100)}
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
std::this_thread::sleep_for(Random::milliseconds(1, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
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("confirm frame sequencing works", "[animation-new]") {
|
||||||
|
textures::init();
|
||||||
|
animation::init();
|
||||||
|
|
||||||
|
auto anim = crafter();
|
||||||
|
|
||||||
|
auto blanket = textures::get_sprite("ritual_crafting_area");
|
||||||
|
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(blanket.sprite->getTextureRect() != init_rect);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
anim.onFrame = [&](){
|
||||||
|
anim.apply(*blanket.sprite);
|
||||||
|
};
|
||||||
|
|
||||||
|
while(anim.playing) {
|
||||||
|
anim.update();
|
||||||
|
// NOTE: possibly find a way to only run apply on frame change?
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(loop_ran == true);
|
||||||
|
REQUIRE(anim.playing == false);
|
||||||
|
|
||||||
|
// this confirms it went back to the first frame
|
||||||
|
REQUIRE(blanket.sprite->getTextureRect() == init_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("confirm transition changes work", "[animation-new]") {
|
||||||
|
textures::init();
|
||||||
|
animation::init();
|
||||||
|
|
||||||
|
auto anim = crafter();
|
||||||
|
|
||||||
|
sf::Vector2f pos{0,0};
|
||||||
|
sf::Vector2f scale{0,0};
|
||||||
|
|
||||||
|
// also testing that onFrame being null means it's not run
|
||||||
|
REQUIRE(anim.onFrame == nullptr);
|
||||||
|
|
||||||
|
anim.play();
|
||||||
|
|
||||||
|
while(anim.playing) {
|
||||||
|
anim.update();
|
||||||
|
anim.motion(pos, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(anim.playing == false);
|
||||||
|
|
||||||
|
REQUIRE(scale != sf::Vector2f{0,0});
|
||||||
|
REQUIRE(pos != sf::Vector2f{0,0});
|
||||||
|
}
|
||||||
|
|
@ -1,296 +1,11 @@
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include "animation.hpp"
|
#include "animation.hpp"
|
||||||
#include "textures.hpp"
|
#include "textures.hpp"
|
||||||
#include "dinkyecs.hpp"
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
|
||||||
#include <chrono>
|
|
||||||
#include <thread>
|
|
||||||
#include "rand.hpp"
|
#include "rand.hpp"
|
||||||
|
|
||||||
using namespace components;
|
using namespace components;
|
||||||
using namespace textures;
|
using namespace textures;
|
||||||
using namespace std::chrono_literals;
|
|
||||||
|
|
||||||
struct Sheet {
|
|
||||||
int width{0};
|
|
||||||
int height{0};
|
|
||||||
int frame_width{0};
|
|
||||||
int frame_height{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Sequence {
|
|
||||||
std::vector<int> frames{};
|
|
||||||
std::vector<std::chrono::milliseconds> 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<sf::Shader> 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(Sequence& seq, Transform& tr)>;
|
|
||||||
using OnFrameHandler = std::function<void()>;
|
|
||||||
|
|
||||||
bool DefaultOnLoop(Sequence& seq, Transform& tr) {
|
|
||||||
seq.current = 0;
|
|
||||||
return tr.looped;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Animate2 {
|
|
||||||
public:
|
|
||||||
Sheet $sheet;
|
|
||||||
Sequence $sequence;
|
|
||||||
Transform $transform;
|
|
||||||
OnFrameHandler onFrame = nullptr;
|
|
||||||
|
|
||||||
std::vector<sf::IntRect> $frame_rects{calc_frames()};
|
|
||||||
OnLoopHandler onLoop = DefaultOnLoop;
|
|
||||||
bool playing = false;
|
|
||||||
|
|
||||||
std::vector<sf::IntRect> calc_frames() {
|
|
||||||
std::vector<sf::IntRect> frames;
|
|
||||||
|
|
||||||
for(int frame_i : $sequence.frames) {
|
|
||||||
frames.emplace_back(
|
|
||||||
sf::Vector2i{$sheet.frame_width * frame_i, 0}, // NOTE: one row only for now
|
|
||||||
sf::Vector2i{$sheet.frame_width,
|
|
||||||
$sheet.frame_height});
|
|
||||||
}
|
|
||||||
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
// NOTE: possibly find a way to only run apply on frame change?
|
|
||||||
void apply(sf::Sprite& sprite, sf::Vector2f pos) {
|
|
||||||
dbc::check($sequence.current < $frame_rects.size(), "current frame past $frame_rects");
|
|
||||||
// NOTE: pos is not updated yet
|
|
||||||
auto& rect = $frame_rects.at($sequence.current);
|
|
||||||
sprite.setTextureRect(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaces step
|
|
||||||
void update_frame() {
|
|
||||||
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 duration = $sequence.durations.at($sequence.current);
|
|
||||||
bool frame_change = false;
|
|
||||||
|
|
||||||
if($sequence.timer.getElapsedTime() >= duration) {
|
|
||||||
$sequence.timer.restart();
|
|
||||||
$sequence.current++;
|
|
||||||
frame_change = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($sequence.current >= $sequence.frame_count) {
|
|
||||||
$sequence.loop_count++;
|
|
||||||
playing = onLoop($sequence, $transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(frame_change && onFrame != nullptr) onFrame();
|
|
||||||
|
|
||||||
dbc::check($sequence.current < $sequence.frame_count, "onLoop fail: current frame out of frames.size()");
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() {
|
|
||||||
update_frame();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Animate2 crafter() {
|
|
||||||
Sheet sheet{
|
|
||||||
.width{720*2},
|
|
||||||
.height{720},
|
|
||||||
.frame_width{720},
|
|
||||||
.frame_height{720},
|
|
||||||
};
|
|
||||||
|
|
||||||
Sequence sequence{
|
|
||||||
.frames{0,1},
|
|
||||||
.durations{Random::milliseconds(1, 100), Random::milliseconds(1, 100)}
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
std::this_thread::sleep_for(Random::milliseconds(1, 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
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("confirm frame sequencing works", "[animation-new]") {
|
|
||||||
textures::init();
|
|
||||||
animation::init();
|
|
||||||
|
|
||||||
auto anim = crafter();
|
|
||||||
|
|
||||||
auto blanket = textures::get_sprite("ritual_crafting_area");
|
|
||||||
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(blanket.sprite->getTextureRect() != init_rect);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
anim.onFrame = [&](){
|
|
||||||
anim.apply(*blanket.sprite, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
sf::Vector2f pos{0,0};
|
|
||||||
|
|
||||||
while(anim.playing) {
|
|
||||||
anim.update();
|
|
||||||
// NOTE: possibly find a way to only run apply on frame change?
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(loop_ran == true);
|
|
||||||
REQUIRE(anim.playing == false);
|
|
||||||
|
|
||||||
// this confirms it went back to the first frame
|
|
||||||
REQUIRE(blanket.sprite->getTextureRect() == init_rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("confirm transition changes work", "[animation-new]") {
|
|
||||||
return;
|
|
||||||
textures::init();
|
|
||||||
animation::init();
|
|
||||||
|
|
||||||
auto blanket = textures::get_sprite("ritual_crafting_area");
|
|
||||||
sf::Vector2f pos{0,0};
|
|
||||||
auto anim = crafter();
|
|
||||||
|
|
||||||
anim.play();
|
|
||||||
while(anim.playing) {
|
|
||||||
anim.update();
|
|
||||||
anim.apply(*blanket.sprite, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(anim.playing == false);
|
|
||||||
|
|
||||||
REQUIRE(blanket.sprite->getPosition() != sf::Vector2f{0,0});
|
|
||||||
REQUIRE(pos != sf::Vector2f{0,0});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("animation easing tests", "[animation]") {
|
TEST_CASE("animation easing tests", "[animation]") {
|
||||||
Animation anim;
|
Animation anim;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics/Sprite.hpp>
|
||||||
|
#include <SFML/Graphics/Image.hpp>
|
||||||
|
#include <SFML/Graphics/Texture.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "matrix.hpp"
|
#include "matrix.hpp"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#include "textures.hpp"
|
#include "textures.hpp"
|
||||||
#include <SFML/Graphics/Sprite.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
#include <SFML/Graphics/Texture.hpp>
|
|
||||||
#include <SFML/Graphics/RenderWindow.hpp>
|
|
||||||
#include <SFML/System.hpp>
|
#include <SFML/System.hpp>
|
||||||
#include <SFML/Audio.hpp>
|
#include <SFML/Audio.hpp>
|
||||||
#include <SFML/Window/Event.hpp>
|
#include <SFML/Window/Event.hpp>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue