diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index dd254db..995dccc 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -11,7 +11,6 @@ #include "core/resources/resource_cache.hpp" #include "external/fkyaml_node.hpp" #include "game/entities/event_bus.hpp" -#include "game/map/map.hpp" #include "utils/defaults.hpp" namespace { @@ -47,9 +46,10 @@ void Player::loadFrom(const std::string& yaml_path) { collision_y_offset = (sprite_h - base_size) - collision_padding; } -Player::Player(const std::string& anim_key, float x, float y, const Controls& controls) +Player::Player(const std::string& anim_key, float x, float y, const Controls& controls, const Map::Info& map_info) : AnimatedSprite(Resource::Cache::get().getAnimationData(anim_key)), - controls_(controls) { + controls_(controls), + map_(map_info) { setPos(x, y); setCurrentAnimation("stand"); } @@ -117,9 +117,9 @@ void Player::update(float delta_time) { setPos(OLD_X, OLD_Y); setPosX(NEW_X); - if (Map::collides(baseBox())) { setPosX(OLD_X); } + if (map_.collides(baseBox())) { setPosX(OLD_X); } setPosY(NEW_Y); - if (Map::collides(baseBox())) { setPosY(OLD_Y); } + if (map_.collides(baseBox())) { setPosY(OLD_Y); } clampToPlayfield(); @@ -157,28 +157,28 @@ auto Player::animationName(bool moving) const -> const char* { } auto Player::getCurrentTile() const -> std::pair { - // El grid del tilemap és de `Map::tile_size` píxels i està 16-alineat + // El grid del tilemap és de `tile_size` píxels i està 16-alineat // respecte a l'origen del tilemap (pot ser negatiu per overscan). El // centre de la caixa de col·lisió ens dóna el tile on "està" el Player. const auto BOX = baseBox(); const float CX = BOX.x + (BOX.w * 0.5F); const float CY = BOX.y + (BOX.h * 0.5F); const int COL = static_cast(std::floor( - (CX - static_cast(Map::tilemap_origin_x)) / static_cast(Map::tile_size))); + (CX - static_cast(map_.tilemap_origin_x)) / static_cast(map_.tile_size))); const int ROW = static_cast(std::floor( - (CY - static_cast(Map::tilemap_origin_y)) / static_cast(Map::tile_size))); + (CY - static_cast(map_.tilemap_origin_y)) / static_cast(map_.tile_size))); return {COL, ROW}; } void Player::clampToPlayfield() { // Límits de la zona jugable: la caixa de col·lisió no pot eixir del // playfield (muros). El cap del sprite sí pot sobresortir per damunt. - const auto MIN_X = static_cast(Map::playfield_x - collision_x_offset); + const auto MIN_X = static_cast(map_.playfield_x - collision_x_offset); const auto MAX_X = static_cast( - Map::playfield_x + Map::playfield_w - collision_x_offset - collision_size); - const auto MIN_Y = static_cast(Map::playfield_y - collision_y_offset); + map_.playfield_x + map_.playfield_w - collision_x_offset - collision_size); + const auto MIN_Y = static_cast(map_.playfield_y - collision_y_offset); const auto MAX_Y = static_cast( - Map::playfield_y + Map::playfield_h - collision_y_offset - collision_size); + map_.playfield_y + map_.playfield_h - collision_y_offset - collision_size); setPosX(std::clamp(getPosX(), MIN_X, MAX_X)); setPosY(std::clamp(getPosY(), MIN_Y, MAX_Y)); } @@ -214,24 +214,24 @@ void Player::snapToLane( const float STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time); const float SAVED = (this->*get_pos)(); (this->*set_pos)(SAVED + std::copysign(STEP, DELTA)); - if (Map::collides(baseBox())) { (this->*set_pos)(SAVED); } + if (map_.collides(baseBox())) { (this->*set_pos)(SAVED); } } } void Player::snapToHorizontalLane(float delta_time) { snapToLane(delta_time, - static_cast(Map::playfield_y - collision_y_offset), - static_cast(Map::pitch_y), - Map::grid_rows, + static_cast(map_.playfield_y - collision_y_offset), + static_cast(map_.pitch_y), + map_.grid_rows, &Player::getPosY, &Player::setPosY); } void Player::snapToVerticalLane(float delta_time) { snapToLane(delta_time, - static_cast(Map::playfield_x - collision_x_offset), - static_cast(Map::pitch_x), - Map::grid_cols, + static_cast(map_.playfield_x - collision_x_offset), + static_cast(map_.pitch_x), + map_.grid_cols, &Player::getPosX, &Player::setPosX); } diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 50d53d4..221b94d 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -7,6 +7,7 @@ #include #include "core/rendering/sprite/animated_sprite.hpp" +#include "game/map/map.hpp" class EventBus; @@ -40,7 +41,7 @@ class Player : public AnimatedSprite { // 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); + Player(const std::string& anim_key, float x, float y, const Controls& controls, const Map::Info& map_info); void update(float delta_time) override; @@ -95,6 +96,12 @@ class Player : public AnimatedSprite { 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 diff --git a/source/game/map/map.cpp b/source/game/map/map.cpp index a10c3ca..a3abf1b 100644 --- a/source/game/map/map.cpp +++ b/source/game/map/map.cpp @@ -104,4 +104,22 @@ namespace Map { return box.x >= static_cast(playfield_x) && box.y >= static_cast(playfield_y) && box.x + box.w <= static_cast(playfield_x + playfield_w) && box.y + box.h <= static_cast(playfield_y + playfield_h); } + auto makeInfo() -> Info { + return { + .playfield_x = playfield_x, + .playfield_y = playfield_y, + .playfield_w = playfield_w, + .playfield_h = playfield_h, + .pitch_x = pitch_x, + .pitch_y = pitch_y, + .grid_cols = grid_cols, + .grid_rows = grid_rows, + .tile_size = tile_size, + .tilemap_origin_x = tilemap_origin_x, + .tilemap_origin_y = tilemap_origin_y, + .collides = &collides, + .inside_playfield = &insidePlayfield, + }; + } + } // namespace Map diff --git a/source/game/map/map.hpp b/source/game/map/map.hpp index 19b8cd0..7b10d24 100644 --- a/source/game/map/map.hpp +++ b/source/game/map/map.hpp @@ -14,6 +14,14 @@ // els membres viuen com a globals del namespace i els callers hi accedeixen // amb `Map::grid_cols`, `Map::collides(...)`, etc. El canvi respecte a // l'anterior `class Map { static ... };` és purament sintàctic. +// +// `MapInfo` (POD trivialment copiable) empaqueta la geometria + dos +// punters a funció de col·lisió per injectar-la a entitats que han +// d'evitar conèixer el namespace `Map::` directament. Construïda via +// `Map::makeInfo()` després de `loadFrom`. Avui només l'usa el Player +// (COUP-02 a docs/audit-game.md); momies, roques i `loadFromLevel` +// continuen llegint `Map::*` directament — la nota original justifica +// que mentre hi hagi un Map global actiu, l'acoblament és acceptable. namespace Map { // Geometria de la graella (carregada de pyramid.yaml > geometry). @@ -73,4 +81,27 @@ namespace Map { // entitats dinàmiques per validar posicions candidates del seu step. [[nodiscard]] auto insidePlayfield(const SDL_FRect& box) -> bool; + // Snapshot plà de la geometria del mapa + punters a `collides` / + // `insidePlayfield`. Pensat per ser passat per valor (POD petit, + // trivialment copiable). Un cop construïda, llegeix els valors + // congelats — si `Map::loadFrom` es torna a cridar amb un mapa + // diferent, cal demanar un MapInfo nou. + struct Info { + int playfield_x{0}; + int playfield_y{0}; + int playfield_w{0}; + int playfield_h{0}; + int pitch_x{0}; + int pitch_y{0}; + int grid_cols{0}; + int grid_rows{0}; + int tile_size{0}; + int tilemap_origin_x{0}; + int tilemap_origin_y{0}; + bool (*collides)(const SDL_FRect&){nullptr}; + bool (*inside_playfield)(const SDL_FRect&){nullptr}; + }; + + [[nodiscard]] auto makeInfo() -> Info; + } // namespace Map diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index a9e65b1..374bb83 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -98,7 +98,7 @@ GameScene::GameScene(Config config) static_cast(Map::playfield_y) + (static_cast(Map::outer_corridor) / 2.0F); const auto SPAWN_X = CORRIDOR_CENTER_X - (static_cast(Player::sprite_w) / 2.0F); const auto SPAWN_Y = CORRIDOR_CENTER_Y - static_cast(Player::collision_y_offset) - (static_cast(Player::collision_size) / 2.0F); - players_.emplace_back(std::make_unique(ANIM_KEY, SPAWN_X, SPAWN_Y, CONTROLS_P1)); + players_.emplace_back(std::make_unique(ANIM_KEY, SPAWN_X, SPAWN_Y, CONTROLS_P1, Map::makeInfo())); // Connectem el Player al bus perquè emeti `on_tile_entered` i els // sarcòfags rebin les notificacions de pas per tile.