feat(entities): caja-peus concèntrica i animacions direccionals a la momia, còpia de data/ sempre sincronitzada; v0.19.0
This commit is contained in:
1 parent
a3592d6c73
commit
32dcf264c4
9 files changed
+251
-58
No files matched your search
@@ -1,5 +1,6 @@
|
||||
#include "game/entities/concrete/mummy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <string>
|
||||
@@ -20,9 +21,36 @@ namespace {
|
||||
return k_rng_;
|
||||
}
|
||||
|
||||
// Llegeix un bloc d'animació del YAML amb fallbacks tolerants (mateix
|
||||
// patró que Door/Sarcophagus). Si la clau no apareix, retorna una
|
||||
// animació d'1 frame loopant, neutra — la momia es podrà moure però
|
||||
// visualment es quedarà estàtica.
|
||||
auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
|
||||
std::vector<int> frames = {0};
|
||||
float fps = 8.0F;
|
||||
bool loop = true;
|
||||
if (yaml.contains("animations") && yaml["animations"].contains(key)) {
|
||||
const auto& anim = yaml["animations"][key];
|
||||
if (anim.contains("frames")) {
|
||||
frames.clear();
|
||||
for (const auto& f : anim["frames"]) {
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
frames.push_back(f.get_value<int>());
|
||||
}
|
||||
if (frames.empty()) { frames.push_back(0); }
|
||||
}
|
||||
if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); }
|
||||
if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); }
|
||||
}
|
||||
return {frames, fps, loop};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Mummy::Mummy(const fkyaml::node& yaml) {
|
||||
Mummy::Mummy(const fkyaml::node& yaml)
|
||||
: anim_walk_down_(buildAnimation(yaml, "walk_down")),
|
||||
anim_walk_up_(buildAnimation(yaml, "walk_up")),
|
||||
anim_walk_lateral_(buildAnimation(yaml, "walk")) {
|
||||
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("mummy");
|
||||
collides_ = true;
|
||||
interactable_ = false;
|
||||
@@ -43,16 +71,36 @@ Mummy::Mummy(const fkyaml::node& yaml) {
|
||||
initial = yaml["ai"]["initial_direction"].get_value<std::string>();
|
||||
}
|
||||
if (initial == "right") {
|
||||
setDirection(1.0F, 0.0F);
|
||||
changeDirection(1.0F, 0.0F);
|
||||
} else if (initial == "left") {
|
||||
setDirection(-1.0F, 0.0F);
|
||||
changeDirection(-1.0F, 0.0F);
|
||||
} else if (initial == "up") {
|
||||
setDirection(0.0F, -1.0F);
|
||||
changeDirection(0.0F, -1.0F);
|
||||
} else if (initial == "down") {
|
||||
setDirection(0.0F, 1.0F);
|
||||
changeDirection(0.0F, 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
void Mummy::changeDirection(float dx, float dy) {
|
||||
setDirection(dx, dy);
|
||||
// Prevalença lateral: si hi ha component horitzontal s'usa l'anim
|
||||
// lateral + flip segons el signe. Només direccions purament verticals
|
||||
// disparen walk_up/walk_down. Quan la direcció queda (0,0) (cul-de-sac
|
||||
// amb NONE) no toquem l'animació: la momia es quedarà al frame actual
|
||||
// fins que step() li torni a assignar direcció.
|
||||
if (dx != 0.0F) {
|
||||
anim_ = anim_walk_lateral_;
|
||||
flip_ = (dx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
|
||||
} else if (dy < 0.0F) {
|
||||
anim_ = anim_walk_up_;
|
||||
flip_ = SDL_FLIP_NONE;
|
||||
} else if (dy > 0.0F) {
|
||||
anim_ = anim_walk_down_;
|
||||
flip_ = SDL_FLIP_NONE;
|
||||
}
|
||||
if (anim_.has_value()) { anim_->reset(); }
|
||||
}
|
||||
|
||||
auto Mummy::chooseDirection(const std::vector<Direction>& allowed) -> Direction {
|
||||
if (allowed.empty()) { return Direction::NONE; }
|
||||
std::uniform_int_distribution<std::size_t> dist(0, allowed.size() - 1);
|
||||
@@ -66,6 +114,31 @@ auto Mummy::candidateBox(float dx, float dy) const -> SDL_FRect {
|
||||
return box;
|
||||
}
|
||||
|
||||
void Mummy::snapToPlayfieldEdge(float dx, float dy) {
|
||||
// La caja 16×16 té exactament l'amplada del corredor; si ens aturàvem
|
||||
// a una posició subpíxel dins de l'última cel·la, qualsevol gir
|
||||
// perpendicular col·lidiria amb el sarc adjacent al limit. Per això
|
||||
// reduïm el dx/dy fins a la vora exacta del playfield en l'eix actiu
|
||||
// (l'altre sempre és 0 perquè la momia només es mou en 1D per frame).
|
||||
const auto BOX = getCollisionRect();
|
||||
float snap_dx = 0.0F;
|
||||
float snap_dy = 0.0F;
|
||||
if (dx > 0.0F) {
|
||||
snap_dx = std::max(0.0F, static_cast<float>(Map::playfield_x + Map::playfield_w) - BOX.x - BOX.w);
|
||||
} else if (dx < 0.0F) {
|
||||
snap_dx = std::min(0.0F, static_cast<float>(Map::playfield_x) - BOX.x);
|
||||
}
|
||||
if (dy > 0.0F) {
|
||||
snap_dy = std::max(0.0F, static_cast<float>(Map::playfield_y + Map::playfield_h) - BOX.y - BOX.h);
|
||||
} else if (dy < 0.0F) {
|
||||
snap_dy = std::min(0.0F, static_cast<float>(Map::playfield_y) - BOX.y);
|
||||
}
|
||||
if (!Map::collides(candidateBox(snap_dx, snap_dy))) {
|
||||
pixel_rect_.x += snap_dx;
|
||||
pixel_rect_.y += snap_dy;
|
||||
}
|
||||
}
|
||||
|
||||
void Mummy::step(float delta_time) {
|
||||
// Desplaçament nominal en aquest frame segons la direcció actual.
|
||||
const float DX = dir_x_ * speed_ * delta_time;
|
||||
@@ -84,60 +157,63 @@ void Mummy::step(float delta_time) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bloqueig: cal girar. Recollim les direccions perpendiculars a l'actual
|
||||
// que tinguin un pròxim tram lliure. Evitem U-turn llevat que no hi hagi
|
||||
// cap alternativa (comportament típic d'Oh Mummy!: no es recula sense
|
||||
// motiu). Si la direcció perpendicular també està bloquejada, no s'hi
|
||||
// inclou; si totes les perpendiculars estan bloquejades, afegim l'opcio
|
||||
// oposada com a últim recurs (cul-de-sac).
|
||||
const float STEP = speed_ * delta_time;
|
||||
// Bloqueig per la vora del playfield: amarrem la caja a la vora exacta.
|
||||
if (!Map::insidePlayfield(CANDIDATE)) { snapToPlayfieldEdge(DX, DY); }
|
||||
|
||||
// Cal girar. Preferim perpendiculars; només fem U-turn si no hi ha cap
|
||||
// altra opció (comportament típic d'Oh Mummy!: no es recula sense motiu).
|
||||
const auto POOLS = collectDirectionPools(speed_ * delta_time);
|
||||
const auto& pool = !POOLS.perp.empty() ? POOLS.perp : POOLS.uturn;
|
||||
applyChoice(chooseDirection(pool));
|
||||
// No avancem aquest frame: la direcció nova s'aplicarà al pròxim step.
|
||||
}
|
||||
|
||||
auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
|
||||
struct Candidate {
|
||||
float dx;
|
||||
float dy;
|
||||
Direction dir;
|
||||
};
|
||||
const std::array<Candidate, 4> ALL{{
|
||||
{.dx = STEP, .dy = 0.0F, .dir = Direction::RIGHT},
|
||||
{.dx = -STEP, .dy = 0.0F, .dir = Direction::LEFT},
|
||||
{.dx = 0.0F, .dy = -STEP, .dir = Direction::UP},
|
||||
{.dx = 0.0F, .dy = STEP, .dir = Direction::DOWN},
|
||||
{.dx = step, .dy = 0.0F, .dir = Direction::RIGHT},
|
||||
{.dx = -step, .dy = 0.0F, .dir = Direction::LEFT},
|
||||
{.dx = 0.0F, .dy = -step, .dir = Direction::UP},
|
||||
{.dx = 0.0F, .dy = step, .dir = Direction::DOWN},
|
||||
}};
|
||||
|
||||
std::vector<Direction> perp_allowed;
|
||||
std::vector<Direction> uturn_allowed;
|
||||
DirectionPools pools;
|
||||
for (const auto& c : ALL) {
|
||||
const bool IS_CURRENT = (c.dx > 0.0F && dir_x_ > 0.0F) || (c.dx < 0.0F && dir_x_ < 0.0F) || (c.dy > 0.0F && dir_y_ > 0.0F) || (c.dy < 0.0F && dir_y_ < 0.0F);
|
||||
const bool IS_UTURN = (c.dx > 0.0F && dir_x_ < 0.0F) || (c.dx < 0.0F && dir_x_ > 0.0F) || (c.dy > 0.0F && dir_y_ < 0.0F) || (c.dy < 0.0F && dir_y_ > 0.0F);
|
||||
if (IS_CURRENT) { continue; } // ja hem vist que està bloquejada
|
||||
const bool IS_CURRENT = (c.dx * dir_x_ > 0.0F) || (c.dy * dir_y_ > 0.0F);
|
||||
if (IS_CURRENT) { continue; } // ja sabem que està bloquejada
|
||||
const auto TEST = candidateBox(c.dx, c.dy);
|
||||
if (!Map::insidePlayfield(TEST) || Map::collides(TEST)) { continue; }
|
||||
const bool IS_UTURN = (c.dx * dir_x_ < 0.0F) || (c.dy * dir_y_ < 0.0F);
|
||||
if (IS_UTURN) {
|
||||
uturn_allowed.push_back(c.dir);
|
||||
pools.uturn.push_back(c.dir);
|
||||
} else {
|
||||
perp_allowed.push_back(c.dir);
|
||||
pools.perp.push_back(c.dir);
|
||||
}
|
||||
}
|
||||
return pools;
|
||||
}
|
||||
|
||||
const auto& pool = !perp_allowed.empty() ? perp_allowed : uturn_allowed;
|
||||
const auto CHOICE = chooseDirection(pool);
|
||||
switch (CHOICE) {
|
||||
void Mummy::applyChoice(Direction dir) {
|
||||
switch (dir) {
|
||||
case Direction::UP:
|
||||
setDirection(0.0F, -1.0F);
|
||||
changeDirection(0.0F, -1.0F);
|
||||
break;
|
||||
case Direction::DOWN:
|
||||
setDirection(0.0F, 1.0F);
|
||||
changeDirection(0.0F, 1.0F);
|
||||
break;
|
||||
case Direction::LEFT:
|
||||
setDirection(-1.0F, 0.0F);
|
||||
changeDirection(-1.0F, 0.0F);
|
||||
break;
|
||||
case Direction::RIGHT:
|
||||
setDirection(1.0F, 0.0F);
|
||||
changeDirection(1.0F, 0.0F);
|
||||
break;
|
||||
case Direction::NONE:
|
||||
setDirection(0.0F, 0.0F);
|
||||
changeDirection(0.0F, 0.0F);
|
||||
break;
|
||||
}
|
||||
// No avancem aquest frame: la direcció nova s'aplicarà al pròxim step.
|
||||
}
|
||||
|
||||
void Mummy::onPlayerContact(Player* /*player*/) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/entities/animation_component.hpp"
|
||||
#include "game/entities/hazard.hpp"
|
||||
|
||||
// Mòmia: perill dinàmic que deambula pels passadís. A cada intersecció
|
||||
@@ -44,4 +45,36 @@ class Mummy : public DynamicHazard {
|
||||
private:
|
||||
// Caixa de col·lisió candidata si l'entitat es desplaçara (dx, dy).
|
||||
[[nodiscard]] auto candidateBox(float dx, float dy) const -> SDL_FRect;
|
||||
|
||||
// Amarra la caja a la vora exacta del playfield quan el candidat hi
|
||||
// sortiria — sense això, amb la caja 16×16 iguala el corredor, la
|
||||
// posició subpíxel dins de l'última cel·la impediria girar a cap
|
||||
// perpendicular (col·lisionaria amb el sarc adjacent). Es crida abans
|
||||
// de la lògica de gir.
|
||||
void snapToPlayfieldEdge(float dx, float dy);
|
||||
|
||||
// Recull les direccions alternatives viables des de la posició actual:
|
||||
// `perpendiculars` (girs) i `uturn` (inversa). La direcció actual s'exclou
|
||||
// perquè ja sabem que està bloquejada. Cada candidata es valida amb
|
||||
// `Map::insidePlayfield` + `Map::collides` sobre la caja resultant.
|
||||
struct DirectionPools {
|
||||
std::vector<Direction> perp;
|
||||
std::vector<Direction> uturn;
|
||||
};
|
||||
[[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools;
|
||||
|
||||
// Canvi de direcció + aplicació de l'animació i flip corresponents.
|
||||
// Tots els camins que trien nova direcció (ctor amb `initial_direction`,
|
||||
// gir després d'un bloqueig) passen per ací per mantenir anim_ i
|
||||
// flip_ en sincronia amb (dir_x_, dir_y_).
|
||||
void changeDirection(float dx, float dy);
|
||||
|
||||
// Aplica la direcció triada (wrapper del switch en una sola crida).
|
||||
void applyChoice(Direction dir);
|
||||
|
||||
// Animacions carregades del YAML, una per cada facing. Per a horitzontal
|
||||
// només en cal una (`walk`) i el flip ja diferencia esquerra/dreta.
|
||||
AnimationComponent anim_walk_down_;
|
||||
AnimationComponent anim_walk_up_;
|
||||
AnimationComponent anim_walk_lateral_;
|
||||
};
|
||||
Reference in new issue
Block a user