Files
aee-arcade/source/game/entities/animation_component.hpp
T

96 lines
4.4 KiB
C++

#pragma once
#include <SDL3/SDL.h>
#include <cstddef>
#include <string>
#include <vector>
#include "external/fkyaml_node.hpp"
// Component reutilitzable d'animació: qualsevol entitat que necessiti
// reproduir una seqüència de frames l'instancia. Es manté com a camp
// opcional dins d'Entity (veure `entity.hpp`) i NO hereta de cap classe
// del pipeline de sprites existent: això manté les entitats estàtiques
// (hazards invisibles, triggers) lliures de l'estat d'animació.
//
// Els frames s'expressen com a índexs enters dins d'un spritesheet. Quan
// algun dia aterri l'art definitiu, el renderer consultarà `currentFrame()`
// i traduirà l'índex al rectangle del sprite corresponent. De moment els
// placeholders tenen un únic frame i `currentFrame()` retorna sempre 0.
//
// El flip (H/V) és propietat de l'animació, no del codi del joc: el YAML
// declara `flip_h`/`flip_v` al bloc d'animació i el component els exposa
// via `flipMode()`. Les entitats apliquen aquest valor al seu `flip_` quan
// canvien d'animació, sense decidir-lo per direcció. Així una mòmia que va
// cap a l'oest reprodueix `walk_west` (frames de l'est + `flip_h: true`)
// sense que el codi de la mòmia toqui flip directament.
//
// El temps es modela com a `speeds_[i]` segons per frame (per-frame), no com
// a `fps` uniforme. Això permet expressar holds al YAML via `frame_ms: [..]`
// (llista) sense haver de repetir el mateix índex de frame. Per a casos
// uniformes la factory omple `speeds_` amb el mateix valor a tot el vector.
class AnimationComponent {
public:
AnimationComponent() = default;
// Factory canònica: llig el bloc `animations.<key>` del YAML.
//
// Camps llegits:
// - `frames` : llista d'índexs sencers (obligatori per animar).
// - `frame_ms` : escalar ms (uniforme) o llista de ms (per-frame).
// Default 1000 ms (animació estàtica).
// - `loop` : bool (default true).
// - `flip_h` : bool (default false).
// - `flip_v` : bool (default false).
//
// Si la clau no existeix retorna un component "estàtic" (1 frame, loop
// true). Si el YAML conté `fps:`, llença `std::runtime_error`: el
// format antic ja no és suportat — cal migrar a `frame_ms:`.
static auto fromYaml(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent;
// Helper uniforme: construeix un component on tots els frames duren
// `frame_ms` mil·lisegons. Útil per a usos programàtics (tests,
// imposició d'un frame estàtic des del loader de nivell). Per al cas
// típic guiat per YAML, usar `fromYaml`.
static auto uniform(std::vector<int> frames, float frame_ms, bool loop) -> AnimationComponent;
static auto uniform(std::vector<int> frames, float frame_ms, bool loop, bool flip_h, bool flip_v) -> AnimationComponent;
// Avanç del temps. Si `frames_` està buit o té un sol element, no fa
// res. En mode loop, `current_` rebota a 0 en passar del final; en
// mode no-loop, es queda al darrer frame i `completed()` passa a true.
// Cada frame consumeix `speeds_[current_]` segons.
void tick(float delta_time);
// Torna al primer frame i reinicia l'acumulador. Útil en transicions
// d'estat (p.ex. Sarcòfag: CLOSED → OPENING reinicia l'animació).
void reset();
// Índex actual dins `frames_`. Si `frames_` està buit retorna 0 com
// a fallback tolerant.
[[nodiscard]] auto currentFrame() const -> int;
[[nodiscard]] auto frameCount() const -> std::size_t { return frames_.size(); }
[[nodiscard]] auto completed() const -> bool { return completed_; }
[[nodiscard]] auto loops() const -> bool { return loop_; }
[[nodiscard]] auto flipH() const -> bool { return flip_h_; }
[[nodiscard]] auto flipV() const -> bool { return flip_v_; }
// Combinació dels dos flags al `SDL_FlipMode` que entén el render.
// SDL3 no admet bitmask: si fos VH simultani, prioritzem H (cap cas
// pràctic actual el necessita combinat).
[[nodiscard]] auto flipMode() const -> SDL_FlipMode;
private:
AnimationComponent(std::vector<int> frames, std::vector<float> speeds, bool loop, bool flip_h, bool flip_v);
std::vector<int> frames_;
std::vector<float> speeds_; // segons per frame; size == frames_.size()
bool loop_{true};
bool flip_h_{false};
bool flip_v_{false};
int current_{0};
float accumulator_{0.0F};
bool completed_{false};
};