migrades portes i plataformes a solidActor

This commit is contained in:
2026-04-11 12:54:54 +02:00
parent 49f6ed41e6
commit 5b2f986d32
22 changed files with 686 additions and 286 deletions

View File

@@ -2,32 +2,31 @@
#include <utility> // Para std::move
#include "game/entities/door.hpp" // Para Door
#include "game/gameplay/collision_map.hpp" // Para CollisionMap
#include "game/gameplay/door_tracker.hpp" // Para DoorTracker
#include "game/gameplay/inventory.hpp" // Para Inventory
#include "game/gameplay/tile_collider.hpp" // Para TileCollider::Tile
#include "utils/defines.hpp" // Para Map::WIDTH, Tile::SIZE
#include "utils/utils.hpp" // Para checkCollision
#include "game/entities/door.hpp" // Para Door
#include "game/gameplay/door_tracker.hpp" // Para DoorTracker
#include "game/gameplay/inventory.hpp" // Para Inventory
#include "game/gameplay/solid_actor_manager.hpp" // Para SolidActorManager
#include "utils/utils.hpp" // Para checkCollision
// Constructor
DoorManager::DoorManager(std::string room_id, CollisionMap* collision_map)
DoorManager::DoorManager(std::string room_id, SolidActorManager* solid_actors)
: room_id_(std::move(room_id)),
collision_map_(collision_map) {
solid_actors_(solid_actors) {
}
// Añade una puerta. Si está bloqueante (CLOSED), pinta sus 4 tiles como WALL
// en el CollisionMap. Si ya está OPENED (porque venía persistida del
// DoorTracker), no se tocan los tiles.
// Añade una puerta y la registra en el SolidActorManager. El bit
// BLOCKS_PLAYER del propio Door determina si bloquea al Player (se setea en
// el constructor si la puerta no arranca ya abierta desde el DoorTracker).
void DoorManager::addDoor(std::shared_ptr<Door> door) { // NOLINT(readability-identifier-naming)
if (door->isBlocking()) {
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::WALL));
}
solid_actors_->registerActor(door.get());
doors_.push_back(std::move(door));
}
// Elimina todas las puertas
// Elimina todas las puertas y las desregistra del SolidActorManager
void DoorManager::clear() {
for (const auto& door : doors_) {
solid_actors_->unregisterActor(door.get());
}
doors_.clear();
}
@@ -36,9 +35,10 @@ void DoorManager::update(float delta_time) {
for (const auto& door : doors_) {
door->update(delta_time);
// Si la puerta acaba de transicionar a OPENED, liberar los tiles y persistir
// Si la puerta acaba de transicionar a OPENED, persistir en DoorTracker.
// El flag BLOCKS_PLAYER ya se limpió dentro de Door::update(), así que
// el sweep del SolidActorManager deja de verla como muro automáticamente.
if (door->justOpened()) {
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::EMPTY));
DoorTracker::get()->addDoor(room_id_, door->getPos());
}
}
@@ -79,48 +79,17 @@ void DoorManager::tryUnlock(const SDL_FRect& player_rect) {
}
#ifdef _DEBUG
// Mueve una puerta del editor: limpia los WALLs viejos, reposiciona el sprite,
// y reescribe los WALLs nuevos si la puerta sigue siendo bloqueante.
// Mueve una puerta del editor: reposiciona su sprite y AABB. El registro
// en el SolidActorManager se mantiene (el manager lee el AABB actual).
void DoorManager::moveDoor(int index, float x, float y) {
if (index < 0 || index >= static_cast<int>(doors_.size())) { return; }
auto& door = doors_[index];
// Limpiar los WALLs viejos antes de mover
if (door->isBlocking()) {
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::EMPTY));
}
// Reposicionar el sprite y el collider del Door
door->setPosition(x, y);
// Re-escribir los WALLs nuevos en la nueva posición si sigue siendo bloqueante
if (door->isBlocking()) {
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::WALL));
}
doors_[index]->setPosition(x, y);
}
// Elimina una puerta del editor, limpiando los WALLs antes de borrarla del vector
// Elimina una puerta del editor, desregistrándola del SolidActorManager
void DoorManager::removeDoor(int index) {
if (index < 0 || index >= static_cast<int>(doors_.size())) { return; }
auto& door = doors_[index];
if (door->isBlocking()) {
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::EMPTY));
}
solid_actors_->unregisterActor(doors_[index].get());
doors_.erase(doors_.begin() + index);
}
#endif
// Setea las 4 celdas que ocupa la puerta (1 col × 4 filas) al valor indicado
void DoorManager::writeDoorTiles(const Door& door, int tile_value) {
// Convertir posición en píxeles a coordenadas de tile
const SDL_FPoint POS = door.getPos();
const int COL = static_cast<int>(POS.x) / Tile::SIZE;
const int ROW = static_cast<int>(POS.y) / Tile::SIZE;
for (int i = 0; i < DOOR_TILES_HEIGHT; ++i) {
const int INDEX = ((ROW + i) * Map::WIDTH) + COL;
collision_map_->setCollisionTile(INDEX, tile_value);
}
}

View File

@@ -8,7 +8,7 @@
#include "game/entities/door.hpp" // Para Door, Door::Data
class CollisionMap;
class SolidActorManager;
/**
* @brief Gestor de puertas de una habitación
@@ -18,14 +18,14 @@ class CollisionMap;
* - Actualizar y renderizar todas las puertas
* - Detectar contacto del jugador con puertas cerradas y disparar la apertura
* si tiene la llave correspondiente (consultando el Inventory global)
* - Sincronizar el estado bloqueante con el CollisionMap: cuando una puerta
* está CLOSED u OPENING, sus 4 tiles son WALL; cuando pasa a OPENED, se
* ponen a EMPTY
* - Registrar cada puerta en el SolidActorManager para que sus AABBs
* participen en los sweeps de colisión del Player. El bit BLOCKS_PLAYER
* del propio Door se encarga de activar/desactivar el bloqueo al abrir.
* - Persistir el estado abierto en DoorTracker
*/
class DoorManager {
public:
DoorManager(std::string room_id, CollisionMap* collision_map);
DoorManager(std::string room_id, SolidActorManager* solid_actors);
~DoorManager() = default;
// Prohibir copia y movimiento para evitar duplicación accidental
@@ -35,7 +35,7 @@ class DoorManager {
auto operator=(DoorManager&&) -> DoorManager& = delete;
// Gestión de puertas
void addDoor(std::shared_ptr<Door> door); // Añade una puerta y aplica WALLs si está cerrada
void addDoor(std::shared_ptr<Door> door); // Añade una puerta y la registra en el SolidActorManager
void clear(); // Elimina todas las puertas
// Actualización y renderizado
@@ -66,24 +66,20 @@ class DoorManager {
/**
* @brief Mueve la puerta indicada a (x, y) en píxeles
*
* Limpia los WALLs viejos del CollisionMap y, si la puerta sigue siendo
* bloqueante, escribe los nuevos. Encapsula el bookkeeping de tiles para
* que el editor nunca toque el CollisionMap directamente.
* Reposiciona el sprite y el AABB de la puerta. El registro en el
* SolidActorManager no cambia: el manager siempre lee el AABB actual
* del SolidActor durante los sweeps.
*/
void moveDoor(int index, float x, float y);
/**
* @brief Elimina la puerta indicada del vector y limpia sus WALLs del CollisionMap
* @brief Elimina la puerta indicada del vector y la desregistra del SolidActorManager
*/
void removeDoor(int index);
#endif
private:
static constexpr int DOOR_TILES_HEIGHT = 4; // Una puerta ocupa 4 tiles verticalmente
void writeDoorTiles(const Door& door, int tile_value); // Setea las 4 celdas en el CollisionMap
std::vector<std::shared_ptr<Door>> doors_; // Colección de puertas
std::string room_id_; // Identificador de la habitación
CollisionMap* collision_map_; // Referencia no propietaria al CollisionMap de la Room
SolidActorManager* solid_actors_; // Referencia no propietaria al SolidActorManager de la Room
};

View File

@@ -1,14 +1,26 @@
#include "platform_manager.hpp"
#include "game/entities/moving_platform.hpp" // Para MovingPlatform
#include "game/entities/moving_platform.hpp" // Para MovingPlatform
#include "game/gameplay/solid_actor_manager.hpp" // Para SolidActorManager
// Añade una plataforma a la colección
// Constructor: recibe el SolidActorManager para registrar las plataformas
// como SolidActors (flags CARRY_ON_TOP | ONEWAY_TOP) y que los sweeps del
// Player las detecten en moveVertical/checkFalling.
PlatformManager::PlatformManager(SolidActorManager* solid_actors)
: solid_actors_(solid_actors) {
}
// Añade una plataforma a la colección y la registra en el SolidActorManager
void PlatformManager::addPlatform(std::shared_ptr<MovingPlatform> platform) {
solid_actors_->registerActor(platform.get());
platforms_.push_back(std::move(platform));
}
// Elimina todas las plataformas
// Elimina todas las plataformas y las desregistra del SolidActorManager
void PlatformManager::clear() {
for (const auto& platform : platforms_) {
solid_actors_->unregisterActor(platform.get());
}
platforms_.clear();
}
@@ -57,40 +69,11 @@ auto PlatformManager::getPlatform(int index) -> std::shared_ptr<MovingPlatform>&
return platforms_.at(index);
}
// Elimina la última plataforma
// Elimina la última plataforma (y la desregistra del SolidActorManager)
void PlatformManager::removeLastPlatform() {
if (!platforms_.empty()) {
solid_actors_->unregisterActor(platforms_.back().get());
platforms_.pop_back();
}
}
#endif
// Comprueba si el jugador está sobre alguna plataforma
// Devuelve puntero a la plataforma o nullptr si no está sobre ninguna
auto PlatformManager::checkPlayerOnPlatform(const SDL_FRect& player_collider, float player_vy) -> MovingPlatform* {
// Solo detectamos si el jugador está cayendo o quieto (no saltando hacia arriba)
if (player_vy < 0.0F) {
return nullptr;
}
for (const auto& platform : platforms_) {
SDL_FRect plat_rect = platform->getCollider();
// Comprobar overlap horizontal
if (player_collider.x + player_collider.w <= plat_rect.x) { continue; }
if (player_collider.x >= plat_rect.x + plat_rect.w) { continue; }
// Comprobar que los pies del jugador están cerca del top de la plataforma
float player_feet = player_collider.y + player_collider.h;
float platform_top = plat_rect.y;
// Tolerancia bidireccional de 4px para compensar el movimiento entre frames
// (cuando la plataforma baja, los pies quedan por encima del top momentáneamente)
constexpr float TOLERANCE = 4.0F;
if (player_feet >= platform_top - TOLERANCE && player_feet <= platform_top + TOLERANCE) {
return platform.get();
}
}
return nullptr;
}

View File

@@ -7,9 +7,11 @@
#include "game/entities/moving_platform.hpp" // Para MovingPlatform, MovingPlatform::Data
class SolidActorManager;
class PlatformManager {
public:
PlatformManager() = default;
explicit PlatformManager(SolidActorManager* solid_actors);
~PlatformManager() = default;
// Prohibir copia y movimiento
@@ -27,10 +29,6 @@ class PlatformManager {
void update(float delta_time);
void render();
// Detección de plataforma bajo el jugador
// Devuelve puntero a la plataforma sobre la que está el jugador, o nullptr
auto checkPlayerOnPlatform(const SDL_FRect& player_collider, float player_vy) -> MovingPlatform*;
#ifdef _DEBUG
void updateAnimations(float delta_time);
void resetPositions(const std::vector<MovingPlatform::Data>& platform_data);
@@ -41,4 +39,5 @@ class PlatformManager {
private:
std::vector<std::shared_ptr<MovingPlatform>> platforms_;
SolidActorManager* solid_actors_{nullptr}; // Referencia no propietaria al SolidActorManager de la Room
};

View File

@@ -2,22 +2,23 @@
#include <utility> // Para std::move
#include "core/rendering/surface.hpp" // Para Surface
#include "core/resources/resource_cache.hpp" // Para Resource
#include "game/defaults.hpp" // Para Defaults::Game
#include "game/gameplay/collision_map.hpp" // Para CollisionMap
#include "game/gameplay/door_manager.hpp" // Para DoorManager
#include "game/gameplay/door_tracker.hpp" // Para DoorTracker
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
#include "game/gameplay/item_manager.hpp" // Para ItemManager
#include "game/gameplay/item_tracker.hpp" // Para ItemTracker
#include "game/gameplay/key_manager.hpp" // Para KeyManager
#include "game/gameplay/key_tracker.hpp" // Para KeyTracker
#include "game/gameplay/platform_manager.hpp" // Para PlatformManager
#include "game/gameplay/room_format.hpp" // Para RoomFormat
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
#include "game/gameplay/tilemap_renderer.hpp" // Para TilemapRenderer
#include "utils/defines.hpp" // Para TILE_SIZE
#include "core/rendering/surface.hpp" // Para Surface
#include "core/resources/resource_cache.hpp" // Para Resource
#include "game/defaults.hpp" // Para Defaults::Game
#include "game/gameplay/collision_map.hpp" // Para CollisionMap
#include "game/gameplay/door_manager.hpp" // Para DoorManager
#include "game/gameplay/door_tracker.hpp" // Para DoorTracker
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
#include "game/gameplay/item_manager.hpp" // Para ItemManager
#include "game/gameplay/item_tracker.hpp" // Para ItemTracker
#include "game/gameplay/key_manager.hpp" // Para KeyManager
#include "game/gameplay/key_tracker.hpp" // Para KeyTracker
#include "game/gameplay/platform_manager.hpp" // Para PlatformManager
#include "game/gameplay/room_format.hpp" // Para RoomFormat
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
#include "game/gameplay/solid_actor_manager.hpp" // Para SolidActorManager
#include "game/gameplay/tilemap_renderer.hpp" // Para TilemapRenderer
#include "utils/defines.hpp" // Para TILE_SIZE
#include "utils/utils.hpp"
// Constructor
@@ -25,18 +26,24 @@ Room::Room(const std::string& room_path, std::shared_ptr<Scoreboard::Data> data)
: data_(std::move(data)) {
auto room = Resource::Cache::get()->getRoom(room_path);
// Gestor de actores sólidos dinámicos (AABBs): puertas, plataformas móviles,
// y otros actores que participan en la resolución de colisión del Player
// sin escribir en el tilemap. Debe ir antes que DoorManager/PlatformManager
// para poder pasarles su puntero.
solid_actor_manager_ = std::make_unique<SolidActorManager>();
// Crea los managers de enemigos, items, plataformas y llaves
enemy_manager_ = std::make_unique<EnemyManager>();
item_manager_ = std::make_unique<ItemManager>(room->number, data_);
platform_manager_ = std::make_unique<PlatformManager>();
platform_manager_ = std::make_unique<PlatformManager>(solid_actor_manager_.get());
key_manager_ = std::make_unique<KeyManager>(room->number);
// Crea el mapa de colisiones desde el collision_tile_map (debe ir antes
// del DoorManager porque éste lo necesita para mutar tiles dinámicamente)
// Crea el mapa de colisiones desde el collision_tile_map
collision_map_ = std::make_unique<CollisionMap>(room->collision_tile_map);
// Crea el manager de puertas (necesita el CollisionMap para sincronizar muros)
door_manager_ = std::make_unique<DoorManager>(room->number, collision_map_.get());
// Crea el manager de puertas (registra los Door como SolidActor en el
// SolidActorManager; ya no escribe tiles en el CollisionMap).
door_manager_ = std::make_unique<DoorManager>(room->number, solid_actor_manager_.get());
initializeRoom(*room);
@@ -250,6 +257,14 @@ auto Room::getTileCollider() const -> const TileCollider& {
return collision_map_->getTileCollider();
}
auto Room::getSolidActors() const -> const SolidActorManager& {
return *solid_actor_manager_;
}
auto Room::getSolidActors() -> SolidActorManager& {
return *solid_actor_manager_;
}
auto Room::getCollisionTileMap() const -> const std::vector<int>& {
return collision_map_->getCollisionTileMap();
}
@@ -258,6 +273,10 @@ void Room::updateCollisionBorders(const CollisionMap::AdjacentData& adjacent) {
collision_map_->updateBorders(adjacent);
}
void Room::updateSolidActorBorders(const SolidActorManager::AdjacentActors& adjacent) {
solid_actor_manager_->setAdjacent(adjacent);
}
// Devuelve la cadena del fichero de la habitación contigua segun el borde
auto Room::getRoom(Border border) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
switch (border) {
@@ -290,10 +309,6 @@ void Room::tryUnlockDoors(const SDL_FRect& player_rect) {
door_manager_->tryUnlock(player_rect);
}
auto Room::checkPlayerOnPlatform(const SDL_FRect& player_collider, float player_vy) -> MovingPlatform* {
return platform_manager_->checkPlayerOnPlatform(player_collider, player_vy);
}
// Carga una habitación desde un archivo YAML (delegado a RoomFormat)
auto Room::loadYAML(const std::string& file_path, bool verbose) -> Data { // NOLINT(readability-convert-member-functions-to-static)
return RoomFormat::loadYAML(file_path, verbose);

View File

@@ -6,15 +6,16 @@
#include <string> // Para string
#include <vector> // Para vector
#include "game/defaults.hpp" // Para Defaults::Game::Room
#include "game/entities/door.hpp" // Para Door::Data
#include "game/entities/enemy.hpp" // Para EnemyData
#include "game/entities/item.hpp" // Para ItemData
#include "game/entities/key.hpp" // Para Key::Data
#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data
#include "game/gameplay/collision_map.hpp" // Para CollisionMap::AdjacentData
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
#include "utils/defines.hpp" // Para Tile::SIZE, Map::WIDTH, Map::HEIGHT
#include "game/defaults.hpp" // Para Defaults::Game::Room
#include "game/entities/door.hpp" // Para Door::Data
#include "game/entities/enemy.hpp" // Para EnemyData
#include "game/entities/item.hpp" // Para ItemData
#include "game/entities/key.hpp" // Para Key::Data
#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data
#include "game/gameplay/collision_map.hpp" // Para CollisionMap::AdjacentData
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
#include "game/gameplay/solid_actor_manager.hpp" // Para SolidActorManager::AdjacentActors
#include "utils/defines.hpp" // Para Tile::SIZE, Map::WIDTH, Map::HEIGHT
class Surface;
class EnemyManager;
class ItemManager;
@@ -82,6 +83,7 @@ class Room {
auto getPlatformManager() -> PlatformManager* { return platform_manager_.get(); }
auto getKeyManager() -> KeyManager* { return key_manager_.get(); }
auto getDoorManager() -> DoorManager* { return door_manager_.get(); }
auto getSolidActorManager() -> SolidActorManager* { return solid_actor_manager_.get(); }
void setTile(int index, int tile_value);
void setCollisionTile(int index, int value);
void setConnection(Border border, const std::string& room_name);
@@ -95,11 +97,13 @@ class Room {
auto itemCollision(SDL_FRect& rect) -> bool;
auto keyCollision(SDL_FRect& rect) -> bool;
void tryUnlockDoors(const SDL_FRect& player_rect);
auto checkPlayerOnPlatform(const SDL_FRect& player_collider, float player_vy) -> MovingPlatform*;
void setPaused(bool value);
[[nodiscard]] auto getTileCollider() const -> const TileCollider&;
[[nodiscard]] auto getSolidActors() const -> const SolidActorManager&;
[[nodiscard]] auto getSolidActors() -> SolidActorManager&;
[[nodiscard]] auto getCollisionTileMap() const -> const std::vector<int>&;
void updateCollisionBorders(const CollisionMap::AdjacentData& adjacent);
void updateSolidActorBorders(const SolidActorManager::AdjacentActors& adjacent);
// Método de carga de archivos YAML (delegado a RoomFormat)
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Data;
@@ -114,6 +118,7 @@ class Room {
std::unique_ptr<PlatformManager> platform_manager_;
std::unique_ptr<KeyManager> key_manager_;
std::unique_ptr<DoorManager> door_manager_;
std::unique_ptr<SolidActorManager> solid_actor_manager_;
std::unique_ptr<CollisionMap> collision_map_;
std::unique_ptr<TilemapRenderer> tilemap_renderer_;
std::shared_ptr<Surface> surface_;

View File

@@ -0,0 +1,197 @@
#include "solid_actor_manager.hpp"
#include <algorithm> // Para std::ranges::find, std::max, std::min
#include "game/entities/solid_actor.hpp" // Para SolidActor
#include "utils/defines.hpp" // Para Collision::NONE, PlayArea::WIDTH/HEIGHT
// Registro de actores
void SolidActorManager::registerActor(SolidActor* actor) {
if (actor == nullptr) { return; }
actors_.push_back(actor);
}
// Desregistro (no borra el objeto — la entidad concreta vive en su propio manager)
void SolidActorManager::unregisterActor(SolidActor* actor) {
auto it = std::ranges::find(actors_, actor);
if (it != actors_.end()) {
actors_.erase(it);
}
}
void SolidActorManager::clear() {
actors_.clear();
}
void SolidActorManager::setAdjacent(const AdjacentActors& adjacent) {
adjacent_ = adjacent;
}
void SolidActorManager::clearAdjacent() {
adjacent_ = {};
}
// Itera sobre todos los actores (local + 4 vecinos cardinales), aplicando
// el offset pertinente al AABB vecino para traerlo al sistema de coordenadas
// de la room actual. Las rooms vecinas tienen sus actores en coordenadas
// locales propias; para verlos desde aquí, se traslada:
// - left: +WIDTH en X (porque en la room actual están a la izquierda del
// borde 0 de coordenadas; trasladándolos por -WIDTH los colocamos "a la
// izquierda" del Player, con x negativas).
// - right: -WIDTH en X (análogo).
// - upper: +HEIGHT en Y.
// - lower: -HEIGHT en Y.
//
// NOTA: el Player siempre está en [0, WIDTH) × [0, HEIGHT) de la room actual.
// Los actores vecinos se muestran con coordenadas fuera de ese rango, y los
// sweeps solo los ven cuando el Player está lo bastante cerca del borde como
// para solapar con ellos.
template <typename Fn>
void SolidActorManager::forEachActor(Fn&& fn) const {
// Locales
for (auto* a : actors_) {
fn(a, a->getAABB());
}
// Adyacente izquierda: sus actores están en [0, WIDTH) de SU room;
// desde la room actual, esos actores están en [-WIDTH, 0). Offset: -WIDTH en X.
if (adjacent_.left != nullptr) {
for (auto* a : adjacent_.left->actors_) {
SDL_FRect r = a->getAABB();
r.x -= PlayArea::WIDTH;
fn(a, r);
}
}
// Adyacente derecha: offset +WIDTH en X.
if (adjacent_.right != nullptr) {
for (auto* a : adjacent_.right->actors_) {
SDL_FRect r = a->getAABB();
r.x += PlayArea::WIDTH;
fn(a, r);
}
}
// Adyacente superior: offset -HEIGHT en Y (arriba tiene y negativa).
if (adjacent_.upper != nullptr) {
for (auto* a : adjacent_.upper->actors_) {
SDL_FRect r = a->getAABB();
r.y -= PlayArea::HEIGHT;
fn(a, r);
}
}
// Adyacente inferior: offset +HEIGHT en Y.
if (adjacent_.lower != nullptr) {
for (auto* a : adjacent_.lower->actors_) {
SDL_FRect r = a->getAABB();
r.y += PlayArea::HEIGHT;
fn(a, r);
}
}
}
// Devuelve el borde derecho del actor bloqueante "a la izquierda" del Player.
// Criterio: el AABB del actor solapa el rect del Player y el borde izquierdo
// del actor está a la izquierda del borde izquierdo del Player (el Player se
// mete en él por la izquierda).
auto SolidActorManager::checkWallLeft(float px, float py, float pw, float ph) const -> float {
float result = Collision::NONE;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (!a->isBlocking()) { return; }
if (a->isOneWayTop()) { return; } // Los jump-through no son pared lateral
// Y-overlap
if (py + ph <= r.y) { return; }
if (py >= r.y + r.h) { return; }
// X-overlap y "a la izquierda": el Player (px) está dentro del actor.
const float RIGHT_EDGE = r.x + r.w;
if (r.x < px && RIGHT_EDGE > px) {
if (result == Collision::NONE || RIGHT_EDGE > result) {
result = RIGHT_EDGE;
}
}
});
return result;
}
// Devuelve el borde izquierdo del actor bloqueante "a la derecha" del Player.
auto SolidActorManager::checkWallRight(float px, float py, float pw, float ph) const -> float {
float result = Collision::NONE;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (!a->isBlocking()) { return; }
if (a->isOneWayTop()) { return; }
// Y-overlap
if (py + ph <= r.y) { return; }
if (py >= r.y + r.h) { return; }
// X-overlap y "a la derecha": el borde derecho del Player entra en el actor.
const float PLAYER_RIGHT = px + pw;
if (r.x < PLAYER_RIGHT && r.x + r.w > PLAYER_RIGHT) {
if (result == Collision::NONE || r.x < result) {
result = r.x;
}
}
});
return result;
}
// Techo: devuelve el borde inferior del actor bloqueante que está por encima
// del Player y solapa su parte superior.
auto SolidActorManager::checkCeiling(float px, float py, float pw) const -> float {
float result = Collision::NONE;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (!a->isBlocking()) { return; }
if (a->isOneWayTop()) { return; } // Los jump-through no tienen techo
// X-overlap
if (px + pw <= r.x) { return; }
if (px >= r.x + r.w) { return; }
// Y: el actor debe estar por encima del Player (su bottom edge entre py y py+ph)
const float BOTTOM_EDGE = r.y + r.h;
if (r.y < py && BOTTOM_EDGE > py) {
if (result == Collision::NONE || BOTTOM_EDGE > result) {
result = BOTTOM_EDGE;
}
}
});
return result;
}
// Suelo: el actor más cercano (por arriba) cuyo top edge esté entre
// foot_y_cur y foot_y_new. Para ONEWAY_TOP, solo cuentan si foot_y_cur <= r.y.
auto SolidActorManager::checkFloor(float px, float foot_y_cur, float pw, float foot_y_new) const -> FloorHit {
FloorHit hit;
forEachActor([&](SolidActor* a, const SDL_FRect& r) {
// Un actor participa en el suelo si es bloqueante O si tiene CARRY_ON_TOP
// (las plataformas jump-through tienen CARRY_ON_TOP sin BLOCKS_PLAYER).
const bool PARTICIPATES = a->isBlocking() || a->carriesOnTop();
if (!PARTICIPATES) { return; }
// X-overlap
if (px + pw <= r.x) { return; }
if (px >= r.x + r.w) { return; }
// Y: el top del actor debe estar en [foot_y_cur, foot_y_new]
if (r.y < foot_y_cur) { return; } // actor por encima: no es suelo, es techo
if (r.y > foot_y_new) { return; } // actor fuera del rango de caída
// ONEWAY_TOP: ya garantizado por foot_y_cur <= r.y (chequeo implícito arriba)
// Actualizar hit si es más alto (menor y) que el actual
if (hit.y == Collision::NONE || r.y < hit.y) {
hit.y = r.y;
hit.carrier = a->carriesOnTop() ? a : nullptr;
}
});
return hit;
}
// Hay suelo "inmediato" debajo (tolerancia 1 px). Usado por checkFalling para
// no desengancharse de plataformas móviles que bajan/suben pixel a pixel.
auto SolidActorManager::hasGroundBelow(float px, float foot_y, float pw) const -> bool {
bool found = false;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (found) { return; }
const bool PARTICIPATES = a->isBlocking() || a->carriesOnTop();
if (!PARTICIPATES) { return; }
// X-overlap
if (px + pw <= r.x) { return; }
if (px >= r.x + r.w) { return; }
// Top del actor dentro de [foot_y, foot_y + 1]
if (r.y >= foot_y && r.y <= foot_y + 1.0F) {
found = true;
}
});
return found;
}

View File

@@ -0,0 +1,91 @@
#pragma once
#include <SDL3/SDL.h>
#include <vector>
class SolidActor;
/**
* @brief Gestor de actores sólidos dinámicos (AABB) de una habitación.
*
* Paralelo al TileCollider: proporciona sweeps axis-aligned del Player
* contra una lista de AABBs de entidades (puertas, plataformas móviles,
* bloques empujables, ascensores, etc). El manager NO es propietario de
* los actores — sus entidades concretas viven en sus respectivos managers
* (DoorManager, PlatformManager, ...). Este manager solo mantiene una
* lista de raw pointers para consultas de colisión.
*
* Cross-room: igual que CollisionMap::updateBorders, este manager acepta
* punteros a los managers de las rooms adyacentes (setAdjacent). Cuando
* el Player está cerca del borde, los sweeps iteran también sobre los
* actores vecinos, trasladando sus AABBs por ±PlayArea::WIDTH/HEIGHT
* para ponerlos en el sistema de coordenadas de la room actual.
*/
class SolidActorManager {
public:
struct FloorHit {
float y{-1};
SolidActor* carrier{nullptr};
};
struct AdjacentActors {
SolidActorManager* upper{nullptr};
SolidActorManager* lower{nullptr};
SolidActorManager* left{nullptr};
SolidActorManager* right{nullptr};
};
SolidActorManager() = default;
~SolidActorManager() = default;
SolidActorManager(const SolidActorManager&) = delete;
auto operator=(const SolidActorManager&) -> SolidActorManager& = delete;
SolidActorManager(SolidActorManager&&) = delete;
auto operator=(SolidActorManager&&) -> SolidActorManager& = delete;
// Registro de actores (el manager no es propietario)
void registerActor(SolidActor* actor);
void unregisterActor(SolidActor* actor);
void clear();
// Cross-room: punteros a los managers de las rooms adyacentes
void setAdjacent(const AdjacentActors& adjacent);
void clearAdjacent();
// Sweeps del Player (paralelos a TileCollider). Todas las coordenadas
// están en el sistema de la room actual (room-local pixel space).
/// Devuelve el x del borde derecho del primer actor bloqueante cuyo AABB
/// esté "a la izquierda" del Player y solape su rect (px, py, pw, ph).
/// Collision::NONE si no hay colisión.
[[nodiscard]] auto checkWallLeft(float px, float py, float pw, float ph) const -> float;
/// Devuelve el x del borde izquierdo del primer actor bloqueante cuyo AABB
/// esté "a la derecha" del Player y solape su rect. Collision::NONE si no.
[[nodiscard]] auto checkWallRight(float px, float py, float pw, float ph) const -> float;
/// Devuelve el y del borde inferior del primer techo bloqueante que solape
/// la parte superior del Player a (px, py, pw). Collision::NONE si no.
[[nodiscard]] auto checkCeiling(float px, float py, float pw) const -> float;
/// Devuelve el suelo más cercano debajo de los pies del Player dentro del
/// rango vertical [foot_y_cur, foot_y_new]. Los actores ONEWAY_TOP solo
/// colisionan si foot_y_cur <= r.y (el Player venía desde arriba).
/// Si el actor tiene CARRY_ON_TOP, FloorHit.carrier apunta a él.
[[nodiscard]] auto checkFloor(float px, float foot_y_cur, float pw, float foot_y_new) const -> FloorHit;
/// Devuelve true si hay algún actor con top justo debajo de los pies
/// (dentro de 1 px). Usado por checkFalling para no desengancharse de
/// plataformas móviles.
[[nodiscard]] auto hasGroundBelow(float px, float foot_y, float pw) const -> bool;
private:
// Itera sobre todos los actores relevantes (locales + vecinos) aplicando
// el offset correcto a los AABBs vecinos. La lambda recibe (actor, aabb_world).
template <typename Fn>
void forEachActor(Fn&& fn) const;
std::vector<SolidActor*> actors_;
AdjacentActors adjacent_{};
};