Files
aee-arcade/source/game/entities/player.hpp
T
JailDesignerandClaude Opus 4.7 d9f6fb45f8 refactor(sprite): PATCH-01 add tryMove + virtual collisionBox to base
- Sprite: nou hook virtual `collisionBox() -> SDL_FRect` (default
  `pos_`). Subclasses amb falsa perspectiva el sobreescriuen amb una
  sub-caixa més petita.
- MovingSprite: nou `tryMove(dx, dy, CollisionFn)` que aplica eix a
  eix i reverteix l'eix que col·lisiona; deixa lliscar el sprite
  contra obstacles.
- Player: override `collisionBox()` retornant `getBaseBox()` i
  `update()` reescrita per fer `animate(dt) + tryMove(vx*dt, vy*dt,
  collidesFn)` en lloc del patró antic `update + revertir + reaplicar`.

El comportament observable és idèntic: la integració `v*dt` parteix
de la mateixa posició (post-snap), els revertits per col·lisió són
els mateixos i el `MOVED` que controla l'animació stand/walk segueix
comparant amb `FRAME_START_X/Y`.

Smoke tests 13/13 OK; log Game start → Bye! sota xvfb-run idèntic al
baseline. clang-tidy i cppcheck nets sobre els fitxers modificats.

Refs: docs/audit-game.md (PATCH-01).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 08:53:11 +02:00

131 lines
5.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <utility>
#include "core/rendering/sprite/animated_sprite.hpp"
#include "game/map/map.hpp"
class EventBus;
class Player : public AnimatedSprite {
public:
// Mapeig de tecles per al moviment del jugador. Cada jugador local en pot
// tenir unes diferents; així podem afegir-ne més sense tocar el codi del
// Player, només instanciant amb Controls distints.
struct Controls {
SDL_Scancode up;
SDL_Scancode down;
SDL_Scancode left;
SDL_Scancode right;
};
// Geometria carregada des de `data/player/player.yaml`. `sprite_*` són
// la mida del frame (sprite 12×24 amb perspectiva falsa: visualment, els
// 12 píxels superiors són el cap i els 12 inferiors la base).
// La caixa de col·lisió és un quadrat `collision_size` concèntric amb el
// quadrat inferior del sprite (`base_size`). Els offsets els calculem
// al load a partir d'aquests tres valors; el mapa de col·lisions i el
// de dibuix són independents (el sprite pot sobresortir sense afectar).
inline static int sprite_w = 0;
inline static int sprite_h = 0;
inline static int base_size = 0;
inline static int collision_size = 0;
inline static int collision_padding = 0;
inline static int collision_x_offset = 0;
inline static int collision_y_offset = 0;
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
static void loadFrom(const std::string& yaml_path);
Player(const std::string& anim_key, float x, float y, const Controls& controls, const Map::Info& map_info);
void update(float delta_time) override;
// Y de la base del jugador (cara inferior de la caixa de colisió). Usada
// per al Z-order contra els sarcs: peus per sobre d'un sarc → darrere;
// per sota → davant.
[[nodiscard]] auto getFeetY() const -> float {
return getPosY() + static_cast<float>(collision_y_offset + collision_size);
}
// Injecta el bus del nivell. El Player publica `on_tile_entered` cada
// vegada que el centre de la seua caixa de col·lisió entra en una
// cel·la nova del tilemap. Si el bus no s'injecta, no emet res.
void setEventBus(EventBus* bus) { bus_ = bus; }
// Tile actual del jugador (centre de `baseBox()` convertit a
// coordenades de tile del tilemap, usant Map::tile_size i
// Map::tilemap_origin_*). Retorna (col, row).
[[nodiscard]] auto getCurrentTile() const -> std::pair<int, int>;
// Rectangle de col·lisió del jugador, en coordenades del canvas. Usat
// pel mapa de col·lisions i per les entitats que comproven contacte
// amb el jugador (momies, trampes, porta).
[[nodiscard]] auto getBaseBox() const -> SDL_FRect;
// Override del hook de Sprite: `MovingSprite::tryMove` envia aquesta
// caixa al callback de col·lisió. La base del Player és més petita
// que el sprite (el cap pot sobresortir per murs/sarcòfags sense
// bloquejar el moviment).
[[nodiscard]] auto collisionBox() const -> SDL_FRect override { return getBaseBox(); }
private:
// Alias privat històric. Mantingut perquè viu dins de l'implementació
// (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`.
[[nodiscard]] auto baseBox() const -> SDL_FRect { return getBaseBox(); }
// Clamp de la caixa de col·lisió al playfield (no al sprite sencer:
// el cap pot sobresortir pel mur superior sense afectar el joc).
void clampToPlayfield();
// Snap cap al passadís perpendicular a la direcció premuda. Si la caixa
// de col·lisió està a ≤ collision_size/2 d'una lane, s'hi aproxima
// gradualment (a velocitat de moviment). Permet canviar de direcció
// sense haver d'alinear-se píxel-perfect en l'encreuament. Els dos
// wrappers deleguen a `snapToLane` només canviant l'eix actiu.
void snapToHorizontalLane(float delta_time);
void snapToVerticalLane(float delta_time);
// Nom d'animació per al `facing_` actual. Si `moving` és cert s'usa
// la variant `walk*`, altrament la variant `stand*`.
[[nodiscard]] auto animationName(bool moving) const -> const char*;
void snapToLane(
float delta_time,
float base,
float pitch,
int max_idx,
float (Player::*get_pos)() const,
void (Player::*set_pos)(float));
Controls controls_;
// Còpia plana de la geometria del mapa + punters a `collides`/
// `inside_playfield`. POD trivial; copiada al ctor i no muta durant
// la vida del Player. Vol dir que aquest Player només és vàlid per
// a un mapa concret — si el mapa canvia, la Scene ha de
// reinstanciar-lo.
Map::Info map_;
// Direcció "cap on mira" el jugador. Governa quin conjunt d'animacions
// s'aplica (lateral, amunt, avall) i si el sprite es flipeja o no. Els
// moviments purament horitzontals o diagonals prenen `SIDE` (prevalença
// lateral), els purament verticals fan `UP`/`DOWN`. En aturar-se es
// preserva l'última orientació perquè la versió stand coincideixi.
enum class Facing : std::uint8_t { SIDE,
UP,
DOWN };
Facing facing_{Facing::SIDE};
// Bus del nivell (opcional). Propietat externa (EntityManager); el
// Player només en guarda un punter per publicar events.
EventBus* bus_{nullptr};
// Últim tile publicat. Inicialitzats a un sentinel fora de rang perquè
// el primer frame en què el bus estigui connectat ja emeti un event.
int last_tile_col_{-9999};
int last_tile_row_{-9999};
};