refactor(entities): unifica loader d'animacio via AnimationComponent::fromYaml i elimina fps:
This commit is contained in:
1 parent
a779533d61
commit
b76a3eb1f3
21 files changed
+306
-231
No files matched your search
@@ -1,14 +1,87 @@
|
||||
#include "game/entities/animation_component.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop)
|
||||
: AnimationComponent(std::move(frames), fps, loop, false, false) {}
|
||||
namespace {
|
||||
|
||||
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop, bool flip_h, bool flip_v)
|
||||
// Quan el caller no especifica temps, considerem un frame "estàtic" de
|
||||
// 1s. El valor és arbitrari: amb 1 frame el component mai no avança,
|
||||
// així que el speed concret és inert. Amb N frames és el fallback que
|
||||
// assumeix la factory (mateix valor que el default del format antic).
|
||||
constexpr float K_DEFAULT_FRAME_SECONDS = 1.0F;
|
||||
|
||||
// Si el YAML declara temps <= 0 per a algun frame, el saturem a aquest
|
||||
// mínim per a evitar bucles infinits a `tick` (que avancen frame
|
||||
// mentre `accumulator >= speeds[i]`). 1 ms és prou petit per a no
|
||||
// alterar mai una temporització real i prou gran per a no col·lapsar.
|
||||
constexpr float K_MIN_FRAME_SECONDS = 0.001F;
|
||||
|
||||
auto sanitizeSpeed(float seconds) -> float {
|
||||
return seconds > 0.0F ? seconds : K_MIN_FRAME_SECONDS;
|
||||
}
|
||||
|
||||
// Expandeix una llista de temps a la mida de `frame_count`:
|
||||
// - mida = N: 1:1.
|
||||
// - mida < N: emplena la resta amb l'últim valor (o el default si
|
||||
// la llista era buida).
|
||||
// - mida > N: talla a N.
|
||||
// Tots els valors es passen per `sanitizeSpeed`.
|
||||
auto fitSpeeds(std::vector<float> speeds, std::size_t frame_count) -> std::vector<float> {
|
||||
if (frame_count == 0) { return {}; }
|
||||
if (speeds.empty()) {
|
||||
return std::vector<float>(frame_count, K_DEFAULT_FRAME_SECONDS);
|
||||
}
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
for (auto& s : speeds) { s = sanitizeSpeed(s); }
|
||||
if (speeds.size() == frame_count) { return speeds; }
|
||||
if (speeds.size() > frame_count) {
|
||||
speeds.resize(frame_count);
|
||||
return speeds;
|
||||
}
|
||||
const float TAIL = speeds.back();
|
||||
speeds.resize(frame_count, TAIL);
|
||||
return speeds;
|
||||
}
|
||||
|
||||
// Llig `animations.<key>.frames` com a llista d'enters. Garanteix
|
||||
// almenys 1 frame (fallback a [0]) per a poder construir sempre un
|
||||
// component vàlid.
|
||||
auto parseFrames(const fkyaml::node& anim) -> std::vector<int> {
|
||||
std::vector<int> frames;
|
||||
for (const auto& f : anim["frames"]) {
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
frames.push_back(f.get_value<int>());
|
||||
}
|
||||
if (frames.empty()) { frames.push_back(0); }
|
||||
return frames;
|
||||
}
|
||||
|
||||
// Llig `animations.<key>.frame_ms` (escalar o llista) i retorna els
|
||||
// temps en segons. Si és escalar, retorna una llista uniforme de
|
||||
// `frame_count` elements; si és llista, copia tal qual (l'ajust de
|
||||
// mida el farà `fitSpeeds` al constructor).
|
||||
auto parseFrameMs(const fkyaml::node& anim, std::size_t frame_count) -> std::vector<float> {
|
||||
std::vector<float> speeds_secs;
|
||||
const auto& fm = anim["frame_ms"];
|
||||
if (fm.is_sequence()) {
|
||||
for (const auto& v : fm) {
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
speeds_secs.push_back(static_cast<float>(v.get_value<int>()) / 1000.0F);
|
||||
}
|
||||
} else {
|
||||
const float SECS = static_cast<float>(fm.get_value<int>()) / 1000.0F;
|
||||
speeds_secs.assign(frame_count, SECS);
|
||||
}
|
||||
return speeds_secs;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AnimationComponent::AnimationComponent(std::vector<int> frames, std::vector<float> speeds, bool loop, bool flip_h, bool flip_v)
|
||||
: frames_(std::move(frames)),
|
||||
fps_(fps > 0.0F ? fps : 1.0F),
|
||||
speeds_(fitSpeeds(std::move(speeds), frames_.size())),
|
||||
loop_(loop),
|
||||
flip_h_(flip_h),
|
||||
flip_v_(flip_v) {
|
||||
@@ -19,13 +92,52 @@ AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool
|
||||
if (frames_.size() <= 1 && !loop_) { completed_ = true; }
|
||||
}
|
||||
|
||||
auto AnimationComponent::uniform(std::vector<int> frames, float frame_ms, bool loop) -> AnimationComponent {
|
||||
return uniform(std::move(frames), frame_ms, loop, false, false);
|
||||
}
|
||||
|
||||
auto AnimationComponent::uniform(std::vector<int> frames, float frame_ms, bool loop, bool flip_h, bool flip_v) -> AnimationComponent {
|
||||
const float SECS = sanitizeSpeed(frame_ms / 1000.0F);
|
||||
std::vector<float> speeds(frames.size(), SECS);
|
||||
return {std::move(frames), std::move(speeds), loop, flip_h, flip_v};
|
||||
}
|
||||
|
||||
auto AnimationComponent::fromYaml(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
|
||||
// Defaults conservadors per a quan el bloc `animations.<key>` no
|
||||
// existeix: 1 frame loopant a 1s — visualment estàtic, semàntica neutra.
|
||||
std::vector<int> frames = {0};
|
||||
std::vector<float> speeds_secs; // buit ⇒ fitSpeeds emplenarà amb default
|
||||
bool loop = true;
|
||||
bool flip_h = false;
|
||||
bool flip_v = false;
|
||||
|
||||
if (!yaml.contains("animations") || !yaml["animations"].contains(key)) {
|
||||
return {std::move(frames), std::move(speeds_secs), loop, flip_h, flip_v};
|
||||
}
|
||||
const auto& anim = yaml["animations"][key];
|
||||
|
||||
// L'antic format `fps: N` ja no és suportat. Si apareix, peguem hard
|
||||
// perquè la migració sigui visible (no silenciosa). El missatge inclou
|
||||
// la clau d'animació per a facilitar localitzar el YAML problemàtic.
|
||||
if (anim.contains("fps")) {
|
||||
throw std::runtime_error("AnimationComponent: 'fps:' is no longer supported, use 'frame_ms:' instead (animation '" + key + "')");
|
||||
}
|
||||
|
||||
if (anim.contains("frames")) { frames = parseFrames(anim); }
|
||||
if (anim.contains("frame_ms")) { speeds_secs = parseFrameMs(anim, frames.size()); }
|
||||
if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); }
|
||||
if (anim.contains("flip_h")) { flip_h = anim["flip_h"].get_value<bool>(); }
|
||||
if (anim.contains("flip_v")) { flip_v = anim["flip_v"].get_value<bool>(); }
|
||||
|
||||
return {std::move(frames), std::move(speeds_secs), loop, flip_h, flip_v};
|
||||
}
|
||||
|
||||
void AnimationComponent::tick(float delta_time) {
|
||||
if (frames_.size() <= 1 || completed_) { return; }
|
||||
|
||||
accumulator_ += delta_time;
|
||||
const float FRAME_TIME = 1.0F / fps_;
|
||||
while (accumulator_ >= FRAME_TIME) {
|
||||
accumulator_ -= FRAME_TIME;
|
||||
while (accumulator_ >= speeds_[static_cast<std::size_t>(current_)]) {
|
||||
accumulator_ -= speeds_[static_cast<std::size_t>(current_)];
|
||||
++current_;
|
||||
if (static_cast<std::size_t>(current_) >= frames_.size()) {
|
||||
if (loop_) {
|
||||
|
||||
Reference in new issue
Block a user