#pragma once #include #include #include #include #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.` 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 frames, float frame_ms, bool loop) -> AnimationComponent; static auto uniform(std::vector 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 frames, std::vector speeds, bool loop, bool flip_h, bool flip_v); std::vector frames_; std::vector 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}; };