feat(anim): cardinal WEST i flip totalment governat pel motor d'animació
This commit is contained in:
1 parent
9dc7ff0da3
commit
72384c3113
15 files changed
+182
-181
No files matched your search
@@ -25,12 +25,16 @@ flags:
|
|||||||
# Strip de 12 frames en graella 4×3 (mateix esquema que el jugador):
|
# Strip de 12 frames en graella 4×3 (mateix esquema que el jugador):
|
||||||
# 0..3 → walk_south (cara avall)
|
# 0..3 → walk_south (cara avall)
|
||||||
# 4..7 → walk_north (cara amunt)
|
# 4..7 → walk_north (cara amunt)
|
||||||
# 8..11 → walk_east (lateral; flip horitzontal segons el sentit)
|
# 8..11 → walk_east (lateral cap a l'est)
|
||||||
|
# La variant `walk_west` reaprofita els frames de l'est amb `flip_h: true`:
|
||||||
|
# el motor d'animació aplica el flip al sprite, així el codi de la mòmia
|
||||||
|
# no decideix flip per direcció (només selecciona quina animació mostrar).
|
||||||
# La momia no té variants `stand_*` perquè mai no para.
|
# La momia no té variants `stand_*` perquè mai no para.
|
||||||
animations:
|
animations:
|
||||||
walk_south: { frames: [0, 1, 2, 3], fps: 8, loop: true }
|
walk_south: { frames: [0, 1, 2, 3], fps: 8, loop: true }
|
||||||
walk_north: { frames: [4, 5, 6, 7], fps: 8, loop: true }
|
walk_north: { frames: [4, 5, 6, 7], fps: 8, loop: true }
|
||||||
walk_east: { frames: [8, 9, 10, 11], fps: 8, loop: true }
|
walk_east: { frames: [8, 9, 10, 11], fps: 8, loop: true }
|
||||||
|
walk_west: { frames: [8, 9, 10, 11], fps: 8, loop: true, flip_h: true }
|
||||||
ai:
|
ai:
|
||||||
type: corridor_wanderer
|
type: corridor_wanderer
|
||||||
direction_choice: random
|
direction_choice: random
|
||||||
|
|||||||
Binary file not shown.
@@ -12,33 +12,43 @@ collision:
|
|||||||
height: 12
|
height: 12
|
||||||
|
|
||||||
animations:
|
animations:
|
||||||
|
# Les variants `*_west` reaprofiten els frames de l'est i declaren
|
||||||
|
# `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
|
||||||
|
# el codi del joc no toca mai `flip_` directament.
|
||||||
stand_south: { frames: [0] }
|
stand_south: { frames: [0] }
|
||||||
stand_north: { frames: [4] }
|
stand_north: { frames: [4] }
|
||||||
stand_east: { frames: [8] }
|
stand_east: { frames: [8] }
|
||||||
|
stand_west: { frames: [8], flip_h: true }
|
||||||
|
|
||||||
jump_south: { frames: [12] }
|
jump_south: { frames: [12] }
|
||||||
jump_north: { frames: [13] }
|
jump_north: { frames: [13] }
|
||||||
jump_east: { frames: [14] }
|
jump_east: { frames: [14] }
|
||||||
|
jump_west: { frames: [14], flip_h: true }
|
||||||
|
|
||||||
stun_south: { frames: [16] }
|
stun_south: { frames: [16] }
|
||||||
stun_north: { frames: [17] }
|
stun_north: { frames: [17] }
|
||||||
stun_east: { frames: [18] }
|
stun_east: { frames: [18] }
|
||||||
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
||||||
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
||||||
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
||||||
|
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
|
||||||
|
|
||||||
recover_south: { frames: [23] }
|
recover_south: { frames: [23] }
|
||||||
recover_north: { frames: [27] }
|
recover_north: { frames: [27] }
|
||||||
recover_east: { frames: [31] }
|
recover_east: { frames: [31] }
|
||||||
|
recover_west: { frames: [31], flip_h: true }
|
||||||
|
|
||||||
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
||||||
# tocs curts (passets d'un tile sense rellisquera).
|
# tocs curts (passets d'un tile sense rellisquera).
|
||||||
|
|||||||
@@ -12,33 +12,43 @@ collision:
|
|||||||
height: 12
|
height: 12
|
||||||
|
|
||||||
animations:
|
animations:
|
||||||
|
# Les variants `*_west` reaprofiten els frames de l'est i declaren
|
||||||
|
# `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
|
||||||
|
# el codi del joc no toca mai `flip_` directament.
|
||||||
stand_south: { frames: [0] }
|
stand_south: { frames: [0] }
|
||||||
stand_north: { frames: [4] }
|
stand_north: { frames: [4] }
|
||||||
stand_east: { frames: [8] }
|
stand_east: { frames: [8] }
|
||||||
|
stand_west: { frames: [8], flip_h: true }
|
||||||
|
|
||||||
jump_south: { frames: [12] }
|
jump_south: { frames: [12] }
|
||||||
jump_north: { frames: [13] }
|
jump_north: { frames: [13] }
|
||||||
jump_east: { frames: [14] }
|
jump_east: { frames: [14] }
|
||||||
|
jump_west: { frames: [14], flip_h: true }
|
||||||
|
|
||||||
stun_south: { frames: [16] }
|
stun_south: { frames: [16] }
|
||||||
stun_north: { frames: [17] }
|
stun_north: { frames: [17] }
|
||||||
stun_east: { frames: [18] }
|
stun_east: { frames: [18] }
|
||||||
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
||||||
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
||||||
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
||||||
|
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
|
||||||
|
|
||||||
recover_south: { frames: [23] }
|
recover_south: { frames: [23] }
|
||||||
recover_north: { frames: [27] }
|
recover_north: { frames: [27] }
|
||||||
recover_east: { frames: [31] }
|
recover_east: { frames: [31] }
|
||||||
|
recover_west: { frames: [31], flip_h: true }
|
||||||
|
|
||||||
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
||||||
# tocs curts (passets d'un tile sense rellisquera).
|
# tocs curts (passets d'un tile sense rellisquera).
|
||||||
|
|||||||
@@ -93,14 +93,13 @@ void AnimatedSprite::animate(float delta_time) {
|
|||||||
|
|
||||||
const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F);
|
const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F);
|
||||||
|
|
||||||
// Helper local per a propagar el frame actual (clip + opcionalment flip)
|
// Helper local per a propagar el frame actual (clip + flip) al sprite.
|
||||||
// al sprite. El flip només s'escriu si l'animació declara `controls_flip`,
|
// El motor sempre sobreescriu el flip — si el YAML no declara `flip_h`/
|
||||||
// per a no trepitjar el flip que el codi del joc encara controla
|
// `flip_v`, el frame té els flags a false i el resultat és `SDL_FLIP_NONE`.
|
||||||
// externament en animacions sense declaració de flip al YAML.
|
|
||||||
auto apply_current_frame = [&](int frame_index) {
|
auto apply_current_frame = [&](int frame_index) {
|
||||||
const auto& frame = anim.frames[frame_index];
|
const auto& frame = anim.frames[frame_index];
|
||||||
setClip(frame.rect);
|
setClip(frame.rect);
|
||||||
if (anim.controls_flip) { setFlip(toFlipMode(frame.flip_h, frame.flip_v)); }
|
setFlip(toFlipMode(frame.flip_h, frame.flip_v));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (anim.accumulated_time >= TOTAL) {
|
if (anim.accumulated_time >= TOTAL) {
|
||||||
@@ -151,7 +150,7 @@ void AnimatedSprite::setCurrentAnimation(const std::string& name) {
|
|||||||
anim.completed = false;
|
anim.completed = false;
|
||||||
if (!anim.frames.empty()) {
|
if (!anim.frames.empty()) {
|
||||||
setClip(anim.frames[0].rect);
|
setClip(anim.frames[0].rect);
|
||||||
if (anim.controls_flip) { setFlip(toFlipMode(anim.frames[0].flip_h, anim.frames[0].flip_v)); }
|
setFlip(toFlipMode(anim.frames[0].flip_h, anim.frames[0].flip_v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
// Dades d'una animació concreta dins d'un spritesheet. Les claus del
|
// Dades d'una animació concreta dins d'un spritesheet. Les claus del
|
||||||
// mapa (`animations_`) són els noms; aquí només es guarden els fotogrames
|
// mapa (`animations_`) són els noms; aquí només es guarden els fotogrames
|
||||||
// i l'estat de reproducció.
|
// i l'estat de reproducció.
|
||||||
|
//
|
||||||
|
// El flip és sempre propietat del motor: a cada `animate()` s'escriu el
|
||||||
|
// flip del frame actual al sprite (default `SDL_FLIP_NONE` quan el YAML
|
||||||
|
// no declara res). El codi del joc no pot — ni ha de — tocar el flip
|
||||||
|
// manualment; ho expressa al YAML declarant `flip_h: true` a l'animació
|
||||||
|
// simètrica (p. ex. `walk_west`) o, per a casos puntuals, per frame
|
||||||
|
// amb la forma mapping `{ index, flip_h?, flip_v? }`.
|
||||||
struct AnimationData {
|
struct AnimationData {
|
||||||
std::vector<Frame> frames;
|
std::vector<Frame> frames;
|
||||||
// Durada de cada frame en segons (convertit des de `frame_ms` al YAML).
|
// Durada de cada frame en segons (convertit des de `frame_ms` al YAML).
|
||||||
@@ -44,13 +51,6 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
// 0 = loop complet des del primer frame.
|
// 0 = loop complet des del primer frame.
|
||||||
// N>0 = rebota al frame N en passar del final.
|
// N>0 = rebota al frame N en passar del final.
|
||||||
int loop_from{0};
|
int loop_from{0};
|
||||||
// Si true, `animate()` escriu el flip del frame actual al sprite.
|
|
||||||
// Si false (cas backward-compat per a animacions sense cap declaració
|
|
||||||
// de flip), `animate()` no toca el flip — el codi del joc el segueix
|
|
||||||
// controlant externament via `setFlip(...)`. Es marca a true quan
|
|
||||||
// l'animació declara `flip_h`/`flip_v`, o quan algun frame es declara
|
|
||||||
// amb la forma mapping `{ index, flip_h?, flip_v? }`.
|
|
||||||
bool controls_flip{false};
|
|
||||||
bool completed{false};
|
bool completed{false};
|
||||||
int current_frame{0};
|
int current_frame{0};
|
||||||
float accumulated_time{0.0F};
|
float accumulated_time{0.0F};
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#include <algorithm>
|
|
||||||
#include <ranges>
|
|
||||||
|
|
||||||
#include "core/rendering/sprite/animated_sprite.hpp"
|
#include "core/rendering/sprite/animated_sprite.hpp"
|
||||||
|
|
||||||
// Implementació del parser pure de YAML d'animacions. Viu en una unitat
|
// Implementació del parser pure de YAML d'animacions. Viu en una unitat
|
||||||
@@ -53,16 +50,6 @@ namespace {
|
|||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detecta si dins de `frames_node` hi ha algun element en forma mapping.
|
|
||||||
// Si n'hi ha (encara que els flags interns siguin false), l'animació
|
|
||||||
// passa a controlar el flip — el mapping és senyal explícit que l'autor
|
|
||||||
// del YAML vol governar el flip per frame.
|
|
||||||
auto framesContainMapping(const fkyaml::node& frames_node) -> bool {
|
|
||||||
return std::ranges::any_of(frames_node, [](const auto& frame_node) {
|
|
||||||
return frame_node.is_mapping();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `frame_ms` pot ser un escalar (temps uniforme) o una llista (per-frame).
|
// `frame_ms` pot ser un escalar (temps uniforme) o una llista (per-frame).
|
||||||
// Convertim sempre a segons perquè `animate()` treballa amb delta_time
|
// Convertim sempre a segons perquè `animate()` treballa amb delta_time
|
||||||
// en segons. Si falta o val 0, `speeds` queda buit i l'animació es
|
// en segons. Si falta o val 0, `speeds` queda buit i l'animació es
|
||||||
@@ -116,14 +103,7 @@ auto AnimatedSprite::parseAnimationsFromYaml(const fkyaml::node& yaml, const She
|
|||||||
|
|
||||||
AnimationData animation;
|
AnimationData animation;
|
||||||
if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
|
if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
|
||||||
const auto& frames_node = anim_node["frames"];
|
animation.frames = convertYamlFrames(anim_node["frames"], params, anim_flip_h, anim_flip_v);
|
||||||
animation.frames = convertYamlFrames(frames_node, params, anim_flip_h, anim_flip_v);
|
|
||||||
// L'animació passa a "controlar" el flip si l'autor ha
|
|
||||||
// declarat qualsevol cosa relacionada amb flip — sigui a
|
|
||||||
// nivell d'anim o a través d'un frame en forma mapping.
|
|
||||||
// Sense cap declaració, ens mantenim passius i deixem el
|
|
||||||
// flip al codi del joc (backward-compat amb sam.yaml).
|
|
||||||
animation.controls_flip = anim_flip_h || anim_flip_v || framesContainMapping(frames_node);
|
|
||||||
}
|
}
|
||||||
parseFrameTimes(anim_node, animation);
|
parseFrameTimes(anim_node, animation);
|
||||||
parseLoopPolicy(anim_node, animation);
|
parseLoopPolicy(anim_node, animation);
|
||||||
|
|||||||
@@ -4,9 +4,14 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop)
|
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop)
|
||||||
|
: AnimationComponent(std::move(frames), fps, loop, false, false) {}
|
||||||
|
|
||||||
|
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop, bool flip_h, bool flip_v)
|
||||||
: frames_(std::move(frames)),
|
: frames_(std::move(frames)),
|
||||||
fps_(fps > 0.0F ? fps : 1.0F),
|
fps_(fps > 0.0F ? fps : 1.0F),
|
||||||
loop_(loop) {
|
loop_(loop),
|
||||||
|
flip_h_(flip_h),
|
||||||
|
flip_v_(flip_v) {
|
||||||
// Una animació d'1 sol frame amb loop=false és una animació que ja s'ha
|
// Una animació d'1 sol frame amb loop=false és una animació que ja s'ha
|
||||||
// acabat de reproduir: no hi ha cap transició per mostrar. Marcant-la
|
// acabat de reproduir: no hi ha cap transició per mostrar. Marcant-la
|
||||||
// com a completada des de l'inici permet als consumidors (p.ex. Door)
|
// com a completada des de l'inici permet als consumidors (p.ex. Door)
|
||||||
@@ -46,3 +51,9 @@ auto AnimationComponent::currentFrame() const -> int {
|
|||||||
if (frames_.empty()) { return 0; }
|
if (frames_.empty()) { return 0; }
|
||||||
return frames_[static_cast<std::size_t>(current_)];
|
return frames_[static_cast<std::size_t>(current_)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto AnimationComponent::flipMode() const -> SDL_FlipMode {
|
||||||
|
if (flip_h_) { return SDL_FLIP_HORIZONTAL; }
|
||||||
|
if (flip_v_) { return SDL_FLIP_VERTICAL; }
|
||||||
|
return SDL_FLIP_NONE;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -13,10 +15,18 @@
|
|||||||
// algun dia aterri l'art definitiu, el renderer consultarà `currentFrame()`
|
// algun dia aterri l'art definitiu, el renderer consultarà `currentFrame()`
|
||||||
// i traduirà l'índex al rectangle del sprite corresponent. De moment els
|
// i traduirà l'índex al rectangle del sprite corresponent. De moment els
|
||||||
// placeholders tenen un únic frame i `currentFrame()` retorna sempre 0.
|
// 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.
|
||||||
class AnimationComponent {
|
class AnimationComponent {
|
||||||
public:
|
public:
|
||||||
AnimationComponent() = default;
|
AnimationComponent() = default;
|
||||||
AnimationComponent(std::vector<int> frames, float fps, bool loop);
|
AnimationComponent(std::vector<int> frames, float fps, bool loop);
|
||||||
|
AnimationComponent(std::vector<int> frames, float fps, bool loop, bool flip_h, bool flip_v);
|
||||||
|
|
||||||
// Avanç del temps. Si `frames_` està buit o té un sol element, no fa
|
// Avanç del temps. Si `frames_` està buit o té un sol element, no fa
|
||||||
// res. En mode loop, `current_frame_` rebota a 0 en passar del final;
|
// res. En mode loop, `current_frame_` rebota a 0 en passar del final;
|
||||||
@@ -37,10 +47,19 @@ class AnimationComponent {
|
|||||||
[[nodiscard]] auto completed() const -> bool { return completed_; }
|
[[nodiscard]] auto completed() const -> bool { return completed_; }
|
||||||
[[nodiscard]] auto loops() const -> bool { return loop_; }
|
[[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:
|
private:
|
||||||
std::vector<int> frames_;
|
std::vector<int> frames_;
|
||||||
float fps_{1.0F};
|
float fps_{1.0F};
|
||||||
bool loop_{true};
|
bool loop_{true};
|
||||||
|
bool flip_h_{false};
|
||||||
|
bool flip_v_{false};
|
||||||
int current_{0};
|
int current_{0};
|
||||||
float accumulator_{0.0F};
|
float accumulator_{0.0F};
|
||||||
bool completed_{false};
|
bool completed_{false};
|
||||||
|
|||||||
@@ -26,11 +26,15 @@ namespace {
|
|||||||
// Llegeix un bloc d'animació del YAML amb fallbacks tolerants (mateix
|
// Llegeix un bloc d'animació del YAML amb fallbacks tolerants (mateix
|
||||||
// patró que Door/Sarcophagus). Si la clau no apareix, retorna una
|
// patró que Door/Sarcophagus). Si la clau no apareix, retorna una
|
||||||
// animació d'1 frame loopant, neutra — la momia es podrà moure però
|
// animació d'1 frame loopant, neutra — la momia es podrà moure però
|
||||||
// visualment es quedarà estàtica.
|
// visualment es quedarà estàtica. Llegeix `flip_h`/`flip_v` (default
|
||||||
|
// false) i els passa al component perquè el motor sàpiga aplicar-los
|
||||||
|
// al sprite quan aquesta animació estigui activa.
|
||||||
auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
|
auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
|
||||||
std::vector<int> frames = {0};
|
std::vector<int> frames = {0};
|
||||||
float fps = 8.0F;
|
float fps = 8.0F;
|
||||||
bool loop = true;
|
bool loop = true;
|
||||||
|
bool flip_h = false;
|
||||||
|
bool flip_v = false;
|
||||||
if (yaml.contains("animations") && yaml["animations"].contains(key)) {
|
if (yaml.contains("animations") && yaml["animations"].contains(key)) {
|
||||||
const auto& anim = yaml["animations"][key];
|
const auto& anim = yaml["animations"][key];
|
||||||
if (anim.contains("frames")) {
|
if (anim.contains("frames")) {
|
||||||
@@ -43,8 +47,10 @@ namespace {
|
|||||||
}
|
}
|
||||||
if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); }
|
if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); }
|
||||||
if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); }
|
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 {frames, fps, loop};
|
return {frames, fps, loop, flip_h, flip_v};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Busca si el segment [prev, prev+delta] travessa una lane del patró
|
// Busca si el segment [prev, prev+delta] travessa una lane del patró
|
||||||
@@ -75,7 +81,8 @@ namespace {
|
|||||||
Mummy::Mummy(const fkyaml::node& yaml)
|
Mummy::Mummy(const fkyaml::node& yaml)
|
||||||
: anim_walk_south_(buildAnimation(yaml, "walk_south")),
|
: anim_walk_south_(buildAnimation(yaml, "walk_south")),
|
||||||
anim_walk_north_(buildAnimation(yaml, "walk_north")),
|
anim_walk_north_(buildAnimation(yaml, "walk_north")),
|
||||||
anim_walk_east_(buildAnimation(yaml, "walk_east")) {
|
anim_walk_east_(buildAnimation(yaml, "walk_east")),
|
||||||
|
anim_walk_west_(buildAnimation(yaml, "walk_west")) {
|
||||||
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("mummy");
|
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("mummy");
|
||||||
collides_ = true;
|
collides_ = true;
|
||||||
interactable_ = false;
|
interactable_ = false;
|
||||||
@@ -125,22 +132,26 @@ Mummy::Mummy(const fkyaml::node& yaml)
|
|||||||
|
|
||||||
void Mummy::changeDirection(float dx, float dy) {
|
void Mummy::changeDirection(float dx, float dy) {
|
||||||
setDirection(dx, dy);
|
setDirection(dx, dy);
|
||||||
// Prevalença lateral: si hi ha component horitzontal s'usa l'anim
|
// Una animació per cada cardinal — el flip viu al YAML
|
||||||
// est + flip segons el signe. Només direccions purament verticals
|
// (`walk_west: flip_h: true`) i el codi només selecciona quina
|
||||||
// disparen walk_north/walk_south. Quan la direcció queda (0,0)
|
// animació mostrar. Prevalença lateral en diagonals; quan la direcció
|
||||||
// (cul-de-sac amb NONE) no toquem l'animació: la momia es quedarà al
|
// queda (0,0) (cul-de-sac amb NONE) no toquem l'animació: la mòmia es
|
||||||
// frame actual fins que step() li torni a assignar direcció.
|
// quedarà al frame actual fins que step() li torni a assignar direcció.
|
||||||
if (dx != 0.0F) {
|
if (dx > 0.0F) {
|
||||||
anim_ = anim_walk_east_;
|
anim_ = anim_walk_east_;
|
||||||
flip_ = (dx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
|
} else if (dx < 0.0F) {
|
||||||
|
anim_ = anim_walk_west_;
|
||||||
} else if (dy < 0.0F) {
|
} else if (dy < 0.0F) {
|
||||||
anim_ = anim_walk_north_;
|
anim_ = anim_walk_north_;
|
||||||
flip_ = SDL_FLIP_NONE;
|
|
||||||
} else if (dy > 0.0F) {
|
} else if (dy > 0.0F) {
|
||||||
anim_ = anim_walk_south_;
|
anim_ = anim_walk_south_;
|
||||||
flip_ = SDL_FLIP_NONE;
|
|
||||||
}
|
}
|
||||||
if (anim_.has_value()) { anim_->reset(); }
|
if (anim_.has_value()) {
|
||||||
|
anim_->reset();
|
||||||
|
// Propaguem el flip que l'animació declara al YAML al camp
|
||||||
|
// `flip_` que el render llegeix (entity_manager_loadlevel.cpp).
|
||||||
|
flip_ = anim_->flipMode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mummy::chooseDirection(const std::vector<Direction>& allowed) -> Direction {
|
auto Mummy::chooseDirection(const std::vector<Direction>& allowed) -> Direction {
|
||||||
|
|||||||
@@ -91,10 +91,11 @@ class Mummy : public DynamicHazard {
|
|||||||
};
|
};
|
||||||
[[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools;
|
[[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools;
|
||||||
|
|
||||||
// Canvi de direcció + aplicació de l'animació i flip corresponents.
|
// Canvi de direcció + selecció de l'animació corresponent (un cardinal
|
||||||
// Tots els camins que trien nova direcció (ctor amb `initial_direction`,
|
// per branch). Tots els camins que trien nova direcció (ctor amb
|
||||||
// gir després d'un bloqueig) passen per ací per mantenir anim_ i
|
// `initial_direction`, gir després d'un bloqueig) passen per ací; el
|
||||||
// flip_ en sincronia amb (dir_x_, dir_y_).
|
// flip viu al YAML (`walk_west: flip_h: true`) i es propaga des de
|
||||||
|
// l'animació al `flip_` heretat.
|
||||||
void changeDirection(float dx, float dy);
|
void changeDirection(float dx, float dy);
|
||||||
|
|
||||||
// Aplica la direcció triada (wrapper del switch en una sola crida).
|
// Aplica la direcció triada (wrapper del switch en una sola crida).
|
||||||
@@ -128,11 +129,14 @@ class Mummy : public DynamicHazard {
|
|||||||
// totes ho estan, retorna `NONE` (la mòmia queda quieta en HURT).
|
// totes ho estan, retorna `NONE` (la mòmia queda quieta en HURT).
|
||||||
[[nodiscard]] auto chooseHurtDirection(Direction preferred) const -> Direction;
|
[[nodiscard]] auto chooseHurtDirection(Direction preferred) const -> Direction;
|
||||||
|
|
||||||
// Animacions carregades del YAML, una per cada facing. Per a horitzontal
|
// Animacions carregades del YAML, una per cada cardinal. La variant
|
||||||
// només en cal una (`walk_east`) i el flip ja diferencia esquerra/dreta.
|
// `walk_west` reaprofita els frames de l'est però amb `flip_h: true`
|
||||||
|
// declarat al YAML — el motor d'animació aplica el flip al sprite
|
||||||
|
// automàticament; aquesta classe no toca mai `flip_` directament.
|
||||||
AnimationComponent anim_walk_south_;
|
AnimationComponent anim_walk_south_;
|
||||||
AnimationComponent anim_walk_north_;
|
AnimationComponent anim_walk_north_;
|
||||||
AnimationComponent anim_walk_east_;
|
AnimationComponent anim_walk_east_;
|
||||||
|
AnimationComponent anim_walk_west_;
|
||||||
|
|
||||||
// Estat HURT i la seua configuració. `hurt_speed_` és l'efectiva
|
// Estat HURT i la seua configuració. `hurt_speed_` és l'efectiva
|
||||||
// (resultat d'aplicar el `speed` del bloc `hurt:` o, si val 0, la
|
// (resultat d'aplicar el `speed` del bloc `hurt:` o, si val 0, la
|
||||||
|
|||||||
@@ -155,10 +155,11 @@ class Entity {
|
|||||||
// el sprite) amb el tile. Nullopt si l'entitat no en té.
|
// el sprite) amb el tile. Nullopt si l'entitat no en té.
|
||||||
[[nodiscard]] auto getCollisionBox() const -> const std::optional<CollisionBox>& { return collision_box_; }
|
[[nodiscard]] auto getCollisionBox() const -> const std::optional<CollisionBox>& { return collision_box_; }
|
||||||
|
|
||||||
// Flip del sprite. Les entitats estàtiques mai no el toquen (queda a
|
// Flip del sprite, propagat des del motor d'animació
|
||||||
// SDL_FLIP_NONE); les dinàmiques que miren cap als costats (p. ex.
|
// (`AnimationComponent::flipMode()`): cap entitat el decideix per
|
||||||
// Mummy en direcció horitzontal) el canvien quan gira. El manager
|
// direcció — el YAML declara `flip_h: true` a l'animació simètrica
|
||||||
// el passa a Surface::render.
|
// (p. ex. `walk_west`) i el setter d'animació l'aplica al `flip_`.
|
||||||
|
// El manager el passa a `Surface::render`.
|
||||||
[[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; }
|
[[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; }
|
||||||
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
||||||
|
|
||||||
|
|||||||
@@ -203,22 +203,21 @@ void Player::readWhipBlock(const fkyaml::node& yaml) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Player::applyCardinal(Cardinal c) {
|
void Player::applyCardinal(Cardinal c) {
|
||||||
|
// El flip del sprite el governa l'animació al motor: només cal posar
|
||||||
|
// el `facing_` que tocaria; `animationName()` ja resol el sufix
|
||||||
|
// (`_west` per Cardinal::WEST) i el YAML hi declara `flip_h: true`.
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case Cardinal::NORTH:
|
case Cardinal::NORTH:
|
||||||
facing_ = Facing::NORTH;
|
facing_ = Facing::NORTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::SOUTH:
|
case Cardinal::SOUTH:
|
||||||
facing_ = Facing::SOUTH;
|
facing_ = Facing::SOUTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::EAST:
|
case Cardinal::EAST:
|
||||||
facing_ = Facing::EAST;
|
facing_ = Facing::EAST;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::WEST:
|
case Cardinal::WEST:
|
||||||
facing_ = Facing::EAST;
|
facing_ = Facing::WEST;
|
||||||
setFlip(SDL_FLIP_HORIZONTAL);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::NONE:
|
case Cardinal::NONE:
|
||||||
break;
|
break;
|
||||||
@@ -313,17 +312,14 @@ void Player::updateFacingAndTurn(float delta_time, float vx, float vy) {
|
|||||||
turning_remaining_ms_ = static_cast<float>(turn_config.duration_ms);
|
turning_remaining_ms_ = static_cast<float>(turn_config.duration_ms);
|
||||||
applyCardinal(intermediate_cardinal_);
|
applyCardinal(intermediate_cardinal_);
|
||||||
} else if (vx != 0.0F) {
|
} else if (vx != 0.0F) {
|
||||||
// Tria de facing original: prevalença lateral, flip segons el
|
// Tria de facing: prevalença lateral, EAST/WEST segons el signe
|
||||||
// signe de vx; verticals purs van a NORTH/SOUTH amb flip NONE;
|
// de vx; verticals purs van a NORTH/SOUTH. En aturar-se
|
||||||
// en aturar-se conservem `facing_` per a l'animació stand.
|
// conservem `facing_` per a l'animació stand.
|
||||||
facing_ = Facing::EAST;
|
facing_ = (vx < 0.0F) ? Facing::WEST : Facing::EAST;
|
||||||
setFlip(vx < 0.0F ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
|
|
||||||
} else if (vy < 0.0F) {
|
} else if (vy < 0.0F) {
|
||||||
facing_ = Facing::NORTH;
|
facing_ = Facing::NORTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
} else if (vy > 0.0F) {
|
} else if (vy > 0.0F) {
|
||||||
facing_ = Facing::SOUTH;
|
facing_ = Facing::SOUTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,11 +651,11 @@ auto Player::currentFacingCardinal() const -> Cardinal {
|
|||||||
case Facing::SOUTH:
|
case Facing::SOUTH:
|
||||||
return Cardinal::SOUTH;
|
return Cardinal::SOUTH;
|
||||||
case Facing::EAST:
|
case Facing::EAST:
|
||||||
break;
|
return Cardinal::EAST;
|
||||||
|
case Facing::WEST:
|
||||||
|
return Cardinal::WEST;
|
||||||
}
|
}
|
||||||
// Lateral: el flip discrimina E/W. Sense flip = mira a la dreta
|
return Cardinal::EAST;
|
||||||
// (convenció estable a tots els assets actuals).
|
|
||||||
return (getFlip() == SDL_FLIP_HORIZONTAL) ? Cardinal::WEST : Cardinal::EAST;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::enterAttacking(Cardinal facing) {
|
void Player::enterAttacking(Cardinal facing) {
|
||||||
@@ -1106,89 +1102,48 @@ void Player::renderShadow() const {
|
|||||||
Screen::get().blendRect(SX, SY, SHADOW_W, SHADOW_H, SHADOW_ARGB, SHADOW_ALPHA);
|
Screen::get().blendRect(SX, SY, SHADOW_W, SHADOW_H, SHADOW_ARGB, SHADOW_ALPHA);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Player::animationName(bool moving) const -> const char* {
|
// Sufix d'animació per cardinal — empat 1:1 amb l'enum `Facing` i el
|
||||||
// En l'aire forcem la variant `jump_*` (per direcció), independent de
|
// sufix declarat als YAMLs de jugador (sam.yaml, morcus.yaml).
|
||||||
// si hi ha velocitat horitzontal: per al jugador, l'estat visual del
|
auto Player::facingSuffix(Facing facing) -> const char* {
|
||||||
// salt és per direcció, no per velocitat.
|
switch (facing) {
|
||||||
if (state_ == State::AIRBORNE) {
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::NORTH:
|
case Facing::NORTH:
|
||||||
return "jump_north";
|
return "_north";
|
||||||
case Facing::SOUTH:
|
case Facing::SOUTH:
|
||||||
return "jump_south";
|
return "_south";
|
||||||
case Facing::EAST:
|
case Facing::EAST:
|
||||||
|
return "_east";
|
||||||
|
case Facing::WEST:
|
||||||
|
return "_west";
|
||||||
|
}
|
||||||
|
return "_east";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix d'animació per estat. ATTACKING té dues sub-fases: durant
|
||||||
|
// `swing_ms` és `attack_*` (animació del braç llançant el látigo); durant
|
||||||
|
// `cooldown_ms` posterior, `recover_*`. A AIRBORNE/LANDING_STUN/HURT, la
|
||||||
|
// variant és per direcció, no per velocitat: l'estat bloqueja el `moving`
|
||||||
|
// argument i no fa servir walk/stand.
|
||||||
|
auto Player::statePrefix(State state, bool moving, bool in_attack_swing) -> const char* {
|
||||||
|
switch (state) {
|
||||||
|
case State::AIRBORNE:
|
||||||
|
return "jump";
|
||||||
|
case State::LANDING_STUN:
|
||||||
|
return "stun";
|
||||||
|
case State::HURT:
|
||||||
|
return "hurt";
|
||||||
|
case State::ATTACKING:
|
||||||
|
return in_attack_swing ? "attack" : "recover";
|
||||||
|
case State::GROUNDED:
|
||||||
|
case State::EXITED:
|
||||||
|
case State::DISABLED:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return "jump_east";
|
return moving ? "walk" : "stand";
|
||||||
}
|
}
|
||||||
// En LANDING_STUN, `stun_*` (per direcció): mostrem el frame
|
|
||||||
// d'aterratge fins que s'esgota el timer. La direcció ve fixada del
|
auto Player::animationName(bool moving) const -> std::string {
|
||||||
// moment del despegue (no es canvia mentre estem en l'aire).
|
const bool IN_ATTACK_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
|
||||||
if (state_ == State::LANDING_STUN) {
|
return std::string(statePrefix(state_, moving, IN_ATTACK_SWING)) + facingSuffix(facing_);
|
||||||
switch (facing_) {
|
|
||||||
case Facing::NORTH:
|
|
||||||
return "stun_north";
|
|
||||||
case Facing::SOUTH:
|
|
||||||
return "stun_south";
|
|
||||||
case Facing::EAST:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return "stun_east";
|
|
||||||
}
|
|
||||||
// En HURT, `hurt_*` (per direcció): el jugador llisca sense
|
|
||||||
// control en la direcció triada per `enterHurt`. Mateixa estructura
|
|
||||||
// que walk per facilitat (ara mateix els frames són clons).
|
|
||||||
if (state_ == State::HURT) {
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::NORTH:
|
|
||||||
return "hurt_north";
|
|
||||||
case Facing::SOUTH:
|
|
||||||
return "hurt_south";
|
|
||||||
case Facing::EAST:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return "hurt_east";
|
|
||||||
}
|
|
||||||
// ATTACKING té dues sub-fases: durant `swing_ms` el sprite és
|
|
||||||
// `attack_*` (animació del braç llançant el látigo); durant
|
|
||||||
// `cooldown_ms` posterior canviem a `recover_*` per a frames
|
|
||||||
// pròpis de recuperació. Mentre l'art no estiga dibuixat, els
|
|
||||||
// YAMLs apunten `recover_*` als mateixos frames que `stand_*` —
|
|
||||||
// visualment és idèntic, però la separació deixa l'arquitectura
|
|
||||||
// preparada perquè en afegir frames reals només calga tocar el
|
|
||||||
// YAML, no el codi.
|
|
||||||
if (state_ == State::ATTACKING) {
|
|
||||||
const bool IN_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
|
|
||||||
if (IN_SWING) {
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::NORTH:
|
|
||||||
return "attack_north";
|
|
||||||
case Facing::SOUTH:
|
|
||||||
return "attack_south";
|
|
||||||
case Facing::EAST:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return "attack_east";
|
|
||||||
}
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::NORTH:
|
|
||||||
return "recover_north";
|
|
||||||
case Facing::SOUTH:
|
|
||||||
return "recover_south";
|
|
||||||
case Facing::EAST:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return "recover_east";
|
|
||||||
}
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::NORTH:
|
|
||||||
return moving ? "walk_north" : "stand_north";
|
|
||||||
case Facing::SOUTH:
|
|
||||||
return moving ? "walk_south" : "stand_south";
|
|
||||||
case Facing::EAST:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return moving ? "walk_east" : "stand_east";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Player::getCurrentTile() const -> std::pair<int, int> {
|
auto Player::getCurrentTile() const -> std::pair<int, int> {
|
||||||
@@ -1252,7 +1207,6 @@ void Player::respawn(float x, float y) {
|
|||||||
// sense això, un EXITED que entrava per la porta amb facing NORTH
|
// sense això, un EXITED que entrava per la porta amb facing NORTH
|
||||||
// re-aparix d'esquena al pròxim cicle. Mateixa lògica que el ctor.
|
// re-aparix d'esquena al pròxim cicle. Mateixa lògica que el ctor.
|
||||||
facing_ = Facing::SOUTH;
|
facing_ = Facing::SOUTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
setCurrentAnimation("stand_south");
|
setCurrentAnimation("stand_south");
|
||||||
setPosX(x);
|
setPosX(x);
|
||||||
setPosY(y);
|
setPosY(y);
|
||||||
|
|||||||
@@ -351,9 +351,12 @@ class Player : public AnimatedSprite {
|
|||||||
void snapToHorizontalLane(float delta_time);
|
void snapToHorizontalLane(float delta_time);
|
||||||
void snapToVerticalLane(float delta_time);
|
void snapToVerticalLane(float delta_time);
|
||||||
|
|
||||||
// Nom d'animació per al `facing_` actual. Si `moving` és cert s'usa
|
// Nom d'animació per al `facing_` actual. Composat com a `prefix +
|
||||||
// la variant `walk*`, altrament la variant `stand*`.
|
// suffix` via els dos helpers estàtics (declarats junt amb `Facing`
|
||||||
[[nodiscard]] auto animationName(bool moving) const -> const char*;
|
// més avall) — el prefix depèn de l'estat (walk/stand/jump/stun/hurt/
|
||||||
|
// attack/recover), el sufix del cardinal (`_north`/`_south`/`_east`/
|
||||||
|
// `_west`).
|
||||||
|
[[nodiscard]] auto animationName(bool moving) const -> std::string;
|
||||||
void snapToLane(
|
void snapToLane(
|
||||||
float delta_time,
|
float delta_time,
|
||||||
float base,
|
float base,
|
||||||
@@ -428,21 +431,24 @@ class Player : public AnimatedSprite {
|
|||||||
// reinstanciar-lo.
|
// reinstanciar-lo.
|
||||||
Map::Info map_;
|
Map::Info map_;
|
||||||
|
|
||||||
// Direcció "cap on mira" el jugador. Governa quin conjunt d'animacions
|
// Direcció "cap on mira" el jugador. Una entrada per cada cardinal: el
|
||||||
// s'aplica (lateral, amunt, avall) i si el sprite es flipeja o no. Els
|
// codi del joc no toca mai el flip — l'oest reprodueix `*_west`, que al
|
||||||
// moviments purament horitzontals o diagonals prenen `EAST` (prevalença
|
// YAML declara `flip_h: true`, i és el motor d'animació qui aplica el
|
||||||
// lateral; el flip horitzontal converteix l'animació EAST en la
|
// flip al sprite. Els moviments diagonals prenen el component lateral
|
||||||
// simètrica oest), els purament verticals fan `NORTH`/`SOUTH`. En
|
// (EAST/WEST); en aturar-se es preserva l'última orientació perquè la
|
||||||
// aturar-se es preserva l'última orientació perquè la versió stand
|
// versió stand coincideixi.
|
||||||
// coincideixi.
|
|
||||||
enum class Facing : std::uint8_t { EAST,
|
enum class Facing : std::uint8_t { EAST,
|
||||||
|
WEST,
|
||||||
NORTH,
|
NORTH,
|
||||||
SOUTH };
|
SOUTH };
|
||||||
Facing facing_{Facing::EAST};
|
Facing facing_{Facing::EAST};
|
||||||
|
|
||||||
|
// Helpers de composició de `animationName`. Han de viure ací (després
|
||||||
|
// de la declaració de `Facing`) perquè la signatura el referencia.
|
||||||
|
static auto facingSuffix(Facing facing) -> const char*;
|
||||||
|
static auto statePrefix(State state, bool moving, bool in_attack_swing) -> const char*;
|
||||||
|
|
||||||
// Cardinal complet (N/S/E/W) usat per a la detecció de girs de 180°.
|
// Cardinal complet (N/S/E/W) usat per a la detecció de girs de 180°.
|
||||||
// El parell (Facing, flip) només té 3+1 estats; aquesta enum n'afegeix
|
|
||||||
// la 4a sense reorganitzar el sistema d'animacions.
|
|
||||||
// Pública perquè el `WhipAttack` la passa a `enterHurt` quan colpeja
|
// Pública perquè el `WhipAttack` la passa a `enterHurt` quan colpeja
|
||||||
// un altre jugador (friendly fire).
|
// un altre jugador (friendly fire).
|
||||||
public:
|
public:
|
||||||
@@ -554,9 +560,8 @@ class Player : public AnimatedSprite {
|
|||||||
// timer `attacking_remaining_ms_` a `swing_ms + cooldown_ms`.
|
// timer `attacking_remaining_ms_` a `swing_ms + cooldown_ms`.
|
||||||
void enterAttacking(Cardinal facing);
|
void enterAttacking(Cardinal facing);
|
||||||
|
|
||||||
// Cardinal que correspon al `(facing_, flip_)` actual del sprite —
|
// Cardinal corresponent al `facing_` actual del sprite — la direcció
|
||||||
// la direcció cap on mira el jugador. Si `facing_ == EAST` el
|
// cap on mira el jugador. Mapeig 1:1 amb `Facing` (mai NONE).
|
||||||
// resultat depèn del flip; NORTH/SOUTH són trivials. Mai retorna NONE.
|
|
||||||
[[nodiscard]] auto currentFacingCardinal() const -> Cardinal;
|
[[nodiscard]] auto currentFacingCardinal() const -> Cardinal;
|
||||||
|
|
||||||
// Activa l'estat HURT: el jugador queda projectat amb velocitat
|
// Activa l'estat HURT: el jugador queda projectat amb velocitat
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
// de càrrega del YAML, no el render real.
|
// de càrrega del YAML, no el render real.
|
||||||
//
|
//
|
||||||
// Tests coberts:
|
// Tests coberts:
|
||||||
// T1 backward compat — YAML sense cap flip declarat carrega tots els
|
// T1 default — YAML sense cap flip declarat carrega tots els frames
|
||||||
// frames amb (flip_h=false, flip_v=false) i sense
|
// amb (flip_h=false, flip_v=false). L'aplicació al sprite
|
||||||
// que l'animació prengui control del flip.
|
// resulta en `SDL_FLIP_NONE`.
|
||||||
// T2 flip a nivell d'animació — `flip_h: true` amb frames `int` →
|
// T2 flip a nivell d'animació — `flip_h: true` amb frames `int` →
|
||||||
// tots els frames hereten flip_h=true.
|
// tots els frames hereten flip_h=true.
|
||||||
// T3 override per-frame — frame mapping `{ index: N, flip_h: true }`
|
// T3 override per-frame — frame mapping `{ index: N, flip_h: true }`
|
||||||
@@ -55,8 +55,8 @@ namespace {
|
|||||||
fkyaml::node::deserialize(yaml_str), sheetParams());
|
fkyaml::node::deserialize(yaml_str), sheetParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
void testBackwardCompat() {
|
void testDefaultNoFlip() {
|
||||||
std::cout << "[T1] backward compat: cap flip declarat\n";
|
std::cout << "[T1] default: cap flip declarat → frames sense flip\n";
|
||||||
const std::string YAML = R"(
|
const std::string YAML = R"(
|
||||||
animations:
|
animations:
|
||||||
walk: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
walk: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
@@ -70,7 +70,6 @@ animations:
|
|||||||
if (WALK_IT != ANIMS.end()) {
|
if (WALK_IT != ANIMS.end()) {
|
||||||
const auto& WALK = WALK_IT->second;
|
const auto& WALK = WALK_IT->second;
|
||||||
REQUIRE(WALK.frames.size() == 4);
|
REQUIRE(WALK.frames.size() == 4);
|
||||||
REQUIRE(!WALK.controls_flip);
|
|
||||||
for (const auto& F : WALK.frames) {
|
for (const auto& F : WALK.frames) {
|
||||||
REQUIRE(!F.flip_h);
|
REQUIRE(!F.flip_h);
|
||||||
REQUIRE(!F.flip_v);
|
REQUIRE(!F.flip_v);
|
||||||
@@ -82,7 +81,6 @@ animations:
|
|||||||
if (STAND_IT != ANIMS.end()) {
|
if (STAND_IT != ANIMS.end()) {
|
||||||
const auto& STAND = STAND_IT->second;
|
const auto& STAND = STAND_IT->second;
|
||||||
REQUIRE(STAND.frames.size() == 1);
|
REQUIRE(STAND.frames.size() == 1);
|
||||||
REQUIRE(!STAND.controls_flip);
|
|
||||||
REQUIRE(!STAND.frames[0].flip_h);
|
REQUIRE(!STAND.frames[0].flip_h);
|
||||||
REQUIRE(!STAND.frames[0].flip_v);
|
REQUIRE(!STAND.frames[0].flip_v);
|
||||||
}
|
}
|
||||||
@@ -100,7 +98,6 @@ animations:
|
|||||||
if (IT != ANIMS.end()) {
|
if (IT != ANIMS.end()) {
|
||||||
const auto& A = IT->second;
|
const auto& A = IT->second;
|
||||||
REQUIRE(A.frames.size() == 4);
|
REQUIRE(A.frames.size() == 4);
|
||||||
REQUIRE(A.controls_flip);
|
|
||||||
for (const auto& F : A.frames) {
|
for (const auto& F : A.frames) {
|
||||||
REQUIRE(F.flip_h);
|
REQUIRE(F.flip_h);
|
||||||
REQUIRE(!F.flip_v);
|
REQUIRE(!F.flip_v);
|
||||||
@@ -126,8 +123,6 @@ animations:
|
|||||||
if (IT != ANIMS.end()) {
|
if (IT != ANIMS.end()) {
|
||||||
const auto& A = IT->second;
|
const auto& A = IT->second;
|
||||||
REQUIRE(A.frames.size() == 4);
|
REQUIRE(A.frames.size() == 4);
|
||||||
// Hi ha un mapping → l'animació pren el control del flip.
|
|
||||||
REQUIRE(A.controls_flip);
|
|
||||||
|
|
||||||
// Els tres primers (int, sense flip a l'anim) → tot a false.
|
// Els tres primers (int, sense flip a l'anim) → tot a false.
|
||||||
REQUIRE(!A.frames[0].flip_h);
|
REQUIRE(!A.frames[0].flip_h);
|
||||||
@@ -160,7 +155,6 @@ animations:
|
|||||||
if (IT != ANIMS.end()) {
|
if (IT != ANIMS.end()) {
|
||||||
const auto& A = IT->second;
|
const auto& A = IT->second;
|
||||||
REQUIRE(A.frames.size() == 2);
|
REQUIRE(A.frames.size() == 2);
|
||||||
REQUIRE(A.controls_flip);
|
|
||||||
|
|
||||||
// Frame int hereta el flip_h true de l'animació.
|
// Frame int hereta el flip_h true de l'animació.
|
||||||
REQUIRE(A.frames[0].flip_h);
|
REQUIRE(A.frames[0].flip_h);
|
||||||
@@ -188,7 +182,6 @@ animations:
|
|||||||
if (IT != ANIMS.end()) {
|
if (IT != ANIMS.end()) {
|
||||||
const auto& A = IT->second;
|
const auto& A = IT->second;
|
||||||
REQUIRE(A.frames.size() == 1);
|
REQUIRE(A.frames.size() == 1);
|
||||||
REQUIRE(A.controls_flip);
|
|
||||||
REQUIRE(A.frames[0].flip_h);
|
REQUIRE(A.frames[0].flip_h);
|
||||||
REQUIRE(A.frames[0].flip_v);
|
REQUIRE(A.frames[0].flip_v);
|
||||||
}
|
}
|
||||||
@@ -199,7 +192,7 @@ animations:
|
|||||||
auto main() -> int {
|
auto main() -> int {
|
||||||
std::cout << "== smoke tests: animation flip parser ==\n";
|
std::cout << "== smoke tests: animation flip parser ==\n";
|
||||||
|
|
||||||
testBackwardCompat();
|
testDefaultNoFlip();
|
||||||
testFlipAtAnimationLevel();
|
testFlipAtAnimationLevel();
|
||||||
testOverridePerFrame();
|
testOverridePerFrame();
|
||||||
testMappingDoesNotInherit();
|
testMappingDoesNotInherit();
|
||||||
|
|||||||
Reference in new issue
Block a user