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

1
.gitignore vendored
View File

@@ -23,3 +23,4 @@ tools/pack_resources/pack_tool.exe
*.res *.res
dist/ dist/
.claude/settings.local.json .claude/settings.local.json
_projecte_2026

View File

@@ -86,6 +86,7 @@ set(APP_SOURCES
source/game/gameplay/key_manager.cpp source/game/gameplay/key_manager.cpp
source/game/gameplay/door_manager.cpp source/game/gameplay/door_manager.cpp
source/game/gameplay/platform_manager.cpp source/game/gameplay/platform_manager.cpp
source/game/gameplay/solid_actor_manager.cpp
source/game/gameplay/item_tracker.cpp source/game/gameplay/item_tracker.cpp
source/game/gameplay/key_tracker.cpp source/game/gameplay/key_tracker.cpp
source/game/gameplay/door_tracker.cpp source/game/gameplay/door_tracker.cpp

View File

@@ -750,16 +750,12 @@ auto MapEditor::commitEntityDrag() -> bool {
break; break;
case EntityType::DOOR: case EntityType::DOOR:
if (IDX >= 0 && IDX < static_cast<int>(room_data_.doors.size())) { if (IDX >= 0 && IDX < static_cast<int>(room_data_.doors.size())) {
// Truco crítico: durante el drag, moveEntityVisual movió el sprite // Con el SolidActorManager, moveDoor solo reposiciona el AABB
// pero los WALLs del CollisionMap siguen en la posición antigua. Antes // del Door (no hay tiles que sincronizar). moveEntityVisual ya
// de llamar a moveDoor (que limpia los tiles "actuales" y escribe los // movió el sprite durante el drag; moveDoor hace el ajuste final.
// nuevos), restauramos el sprite a su posición vieja para que coincida
// con los tiles. moveDoor luego hace el ciclo limpio y completo.
auto* door_mgr = room_->getDoorManager();
door_mgr->getDoor(IDX)->setPosition(room_data_.doors[IDX].x, room_data_.doors[IDX].y);
room_data_.doors[IDX].x = drag_.snap_x; room_data_.doors[IDX].x = drag_.snap_x;
room_data_.doors[IDX].y = drag_.snap_y; room_data_.doors[IDX].y = drag_.snap_y;
door_mgr->moveDoor(IDX, drag_.snap_x, drag_.snap_y); room_->getDoorManager()->moveDoor(IDX, drag_.snap_x, drag_.snap_y);
selection_ = {EntityType::DOOR, IDX}; selection_ = {EntityType::DOOR, IDX};
return true; return true;
} }
@@ -2503,11 +2499,12 @@ auto MapEditor::duplicateKey() -> std::string {
// (id, animation) requiere recrear el Door y mantener los tiles sincronizados. // (id, animation) requiere recrear el Door y mantener los tiles sincronizados.
void MapEditor::rebuildDoors() { void MapEditor::rebuildDoors() {
auto* door_mgr = room_->getDoorManager(); auto* door_mgr = room_->getDoorManager();
// Borrar una a una desde el principio: cada removeDoor limpia sus WALLs // Borrar una a una desde el principio: cada removeDoor desregistra el
// Door del SolidActorManager.
while (door_mgr->getCount() > 0) { while (door_mgr->getCount() > 0) {
door_mgr->removeDoor(0); door_mgr->removeDoor(0);
} }
// Re-añadir desde room_data_; addDoor reescribe los WALLs si bloquean // Re-añadir desde room_data_; addDoor las registra como SolidActor.
for (const auto& d : room_data_.doors) { for (const auto& d : room_data_.doors) {
door_mgr->addDoor(std::make_shared<Door>(d, /*start_opened=*/false)); door_mgr->addDoor(std::make_shared<Door>(d, /*start_opened=*/false));
} }
@@ -2584,7 +2581,7 @@ auto MapEditor::deleteDoor() -> std::string {
const int IDX = selection_.index; const int IDX = selection_.index;
// Importante: usar removeDoor del manager (limpia los WALLs antes de borrar) // Importante: usar removeDoor del manager (desregistra del SolidActorManager)
room_->getDoorManager()->removeDoor(IDX); room_->getDoorManager()->removeDoor(IDX);
room_data_.doors.erase(room_data_.doors.begin() + IDX); room_data_.doors.erase(room_data_.doors.begin() + IDX);

View File

@@ -5,7 +5,9 @@
// Constructor: carga la animación y posiciona la puerta. Si start_opened es // Constructor: carga la animación y posiciona la puerta. Si start_opened es
// true, la puerta se crea ya abierta (estado OPENED, animación "opened"); en // true, la puerta se crea ya abierta (estado OPENED, animación "opened"); en
// caso contrario, se crea cerrada (estado CLOSED, animación "closed"). // caso contrario, se crea cerrada (estado CLOSED, animación "closed") y
// activa el flag BLOCKS_PLAYER del SolidActor para que los sweeps del
// SolidActorManager la vean como muro.
Door::Door(const Data& data, bool start_opened) Door::Door(const Data& data, bool start_opened)
: sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData(data.animation_path))), : sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData(data.animation_path))),
id_(data.id), id_(data.id),
@@ -13,7 +15,10 @@ Door::Door(const Data& data, bool start_opened)
sprite_->setPosX(data.x); sprite_->setPosX(data.x);
sprite_->setPosY(data.y); sprite_->setPosY(data.y);
sprite_->setCurrentAnimation(start_opened ? "opened" : "closed"); sprite_->setCurrentAnimation(start_opened ? "opened" : "closed");
collider_ = sprite_->getRect(); aabb_ = sprite_->getRect();
if (!start_opened) {
flags_ = BLOCKS_PLAYER;
}
} }
// Pinta la puerta en pantalla // Pinta la puerta en pantalla
@@ -23,8 +28,9 @@ void Door::render() {
// Avanza la animación. Solo OPENING anima de verdad; CLOSED y OPENED son // Avanza la animación. Solo OPENING anima de verdad; CLOSED y OPENED son
// frames estáticos. Cuando la animación de OPENING termina, transiciona a // frames estáticos. Cuando la animación de OPENING termina, transiciona a
// OPENED, fija el frame final y marca just_opened_ para que el DoorManager // OPENED, fija el frame final, limpia el flag BLOCKS_PLAYER (los sweeps
// libere los tiles de colisión. // del SolidActorManager dejan de verla como muro) y marca just_opened_
// para que el DoorManager persista el estado abierto en DoorTracker.
void Door::update(float delta_time) { void Door::update(float delta_time) {
if (is_paused_) { if (is_paused_) {
return; return;
@@ -35,6 +41,7 @@ void Door::update(float delta_time) {
if (sprite_->animationIsCompleted()) { if (sprite_->animationIsCompleted()) {
state_ = State::OPENED; state_ = State::OPENED;
sprite_->setCurrentAnimation("opened"); sprite_->setCurrentAnimation("opened");
flags_ = 0; // Deja de bloquear al Player
just_opened_ = true; just_opened_ = true;
} }
} }
@@ -65,11 +72,12 @@ auto Door::justOpened() -> bool {
} }
#ifdef _DEBUG #ifdef _DEBUG
// Mueve la puerta a la posición indicada (sprite + collider). NO toca el // Mueve la puerta a la posición indicada (sprite + AABB del SolidActor).
// CollisionMap — eso es responsabilidad del DoorManager (moveDoor/removeDoor). // Usado por el editor; el DoorManager::moveDoor se encarga del bookkeeping
// de registro en el SolidActorManager.
void Door::setPosition(float x, float y) { void Door::setPosition(float x, float y) {
sprite_->setPosX(x); sprite_->setPosX(x);
sprite_->setPosY(y); sprite_->setPosY(y);
collider_ = sprite_->getRect(); aabb_ = sprite_->getRect();
} }
#endif #endif

View File

@@ -5,6 +5,8 @@
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <string> // Para string #include <string> // Para string
#include "game/entities/solid_actor.hpp" // Para SolidActor
class AnimatedSprite; class AnimatedSprite;
/** /**
@@ -15,12 +17,12 @@ class AnimatedSprite;
* - "opening": animación de transición que se reproduce una sola vez * - "opening": animación de transición que se reproduce una sola vez
* - "opened": estado final no bloqueante (frame estático) * - "opened": estado final no bloqueante (frame estático)
* *
* El comportamiento de "muro" se implementa marcando los 4 tiles que ocupa * El comportamiento de "muro" se implementa como SolidActor con flag
* la puerta como WALL en el CollisionMap (lo gestiona el DoorManager). Cuando * BLOCKS_PLAYER. El SolidActorManager de la Room lo consulta en los sweeps
* la puerta termina de abrirse, los tiles vuelven a EMPTY y el jugador puede * del Player. Cuando la puerta termina de abrirse, se limpia el flag
* pasar. * BLOCKS_PLAYER y el sweep deja de verla como pared.
*/ */
class Door { class Door : public SolidActor {
public: public:
enum class State : int { enum class State : int {
CLOSED = 0, CLOSED = 0,
@@ -41,11 +43,10 @@ class Door {
void render(); // Pinta la puerta en pantalla void render(); // Pinta la puerta en pantalla
void update(float delta_time); // Avanza la animación; si OPENING termina → OPENED void update(float delta_time); // Avanza la animación; si OPENING termina → OPENED
auto getCollider() -> SDL_FRect& { return collider_; } // Rectángulo de colisión (8x32) auto getCollider() -> SDL_FRect& { return aabb_; } // Rectángulo de colisión (8x32)
[[nodiscard]] auto getPos() const -> SDL_FPoint; // Posición en píxeles [[nodiscard]] auto getPos() const -> SDL_FPoint; // Posición en píxeles
[[nodiscard]] auto getId() const -> const std::string& { return id_; } // Identificador [[nodiscard]] auto getId() const -> const std::string& { return id_; } // Identificador
[[nodiscard]] auto getState() const -> State { return state_; } // Estado actual [[nodiscard]] auto getState() const -> State { return state_; } // Estado actual
[[nodiscard]] auto isBlocking() const -> bool { return state_ != State::OPENED; } // True si bloquea al jugador
void startOpening(); // Transición CLOSED → OPENING void startOpening(); // Transición CLOSED → OPENING
auto justOpened() -> bool; // Flag one-shot consumido por el manager auto justOpened() -> bool; // Flag one-shot consumido por el manager
@@ -53,12 +54,11 @@ class Door {
void setPaused(bool paused) { is_paused_ = paused; } // Pausa/despausa la animación void setPaused(bool paused) { is_paused_ = paused; } // Pausa/despausa la animación
#ifdef _DEBUG #ifdef _DEBUG
void setPosition(float x, float y); // Mueve sprite y collider en vivo (editor; NO toca CollisionMap) void setPosition(float x, float y); // Mueve sprite y AABB en vivo (editor)
#endif #endif
private: private:
std::shared_ptr<AnimatedSprite> sprite_; // Sprite animado de la puerta std::shared_ptr<AnimatedSprite> sprite_; // Sprite animado de la puerta
SDL_FRect collider_{}; // Rectángulo de colisión
std::string id_; // Identificador std::string id_; // Identificador
State state_{State::CLOSED}; // Estado actual State state_{State::CLOSED}; // Estado actual
bool just_opened_{false}; // Flag one-shot: la puerta acaba de transicionar a OPENED bool just_opened_{false}; // Flag one-shot: la puerta acaba de transicionar a OPENED

View File

@@ -28,13 +28,19 @@ MovingPlatform::MovingPlatform(const Data& data)
speed_(data.speed), speed_(data.speed),
loop_mode_(data.loop), loop_mode_(data.loop),
easing_(resolveEasing(data.easing)) { easing_(resolveEasing(data.easing)) {
// Flags del SolidActor: jump-through desde abajo, carry al Player encima.
// NOTA: sin BLOCKS_PLAYER, las plataformas móviles no bloquean lateralmente
// ni por arriba. ONEWAY_TOP hace que los sweeps verticales solo las vean
// al caer desde arriba.
flags_ = CARRY_ON_TOP | ONEWAY_TOP;
// Colocar el sprite en el primer waypoint // Colocar el sprite en el primer waypoint
if (!path_.empty()) { if (!path_.empty()) {
sprite_->setPosX(path_[0].x); sprite_->setPosX(path_[0].x);
sprite_->setPosY(path_[0].y); sprite_->setPosY(path_[0].y);
} }
collider_ = getRect(); aabb_ = getRect();
// Frame inicial // Frame inicial
sprite_->setCurrentAnimationFrame((data.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : data.frame); sprite_->setCurrentAnimationFrame((data.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : data.frame);
@@ -115,8 +121,8 @@ void MovingPlatform::update(float delta_time) {
sprite_->animate(delta_time); sprite_->animate(delta_time);
if (path_.size() < 2) { if (path_.size() < 2) {
last_dx_ = 0.0F; last_delta_.x = 0.0F;
last_dy_ = 0.0F; last_delta_.y = 0.0F;
return; return;
} }
@@ -130,8 +136,8 @@ void MovingPlatform::update(float delta_time) {
waiting_ = false; waiting_ = false;
advanceSegment(); advanceSegment();
} else { } else {
last_dx_ = 0.0F; last_delta_.x = 0.0F;
last_dy_ = 0.0F; last_delta_.y = 0.0F;
return; return;
} }
} }
@@ -174,9 +180,9 @@ void MovingPlatform::update(float delta_time) {
sprite_->setPosY(new_y); sprite_->setPosY(new_y);
} }
last_dx_ = sprite_->getPosX() - old_x; last_delta_.x = sprite_->getPosX() - old_x;
last_dy_ = sprite_->getPosY() - old_y; last_delta_.y = sprite_->getPosY() - old_y;
collider_ = getRect(); aabb_ = getRect();
} }
// Pinta la plataforma en pantalla // Pinta la plataforma en pantalla
@@ -208,7 +214,7 @@ void MovingPlatform::resetToInitialPosition(const Data& data) {
sprite_->setPosY(path_[0].y); sprite_->setPosY(path_[0].y);
} }
collider_ = getRect(); aabb_ = getRect();
recalcSegmentLength(); recalcSegmentLength();
} }
#endif #endif
@@ -217,8 +223,3 @@ void MovingPlatform::resetToInitialPosition(const Data& data) {
auto MovingPlatform::getRect() -> SDL_FRect { auto MovingPlatform::getRect() -> SDL_FRect {
return sprite_->getRect(); return sprite_->getRect();
} }
// Obtiene el rectangulo de colisión
auto MovingPlatform::getCollider() -> SDL_FRect& {
return collider_;
}

View File

@@ -6,6 +6,8 @@
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector #include <vector> // Para vector
#include "game/entities/solid_actor.hpp" // Para SolidActor
class AnimatedSprite; class AnimatedSprite;
// Punto de paso en la ruta de una plataforma // Punto de paso en la ruta de una plataforma
@@ -22,7 +24,7 @@ enum class LoopMode { PINGPONG,
// Tipo de función de easing // Tipo de función de easing
using EasingFunc = float (*)(float); using EasingFunc = float (*)(float);
class MovingPlatform { class MovingPlatform : public SolidActor {
public: public:
struct Data { struct Data {
std::string animation_path; std::string animation_path;
@@ -44,10 +46,7 @@ class MovingPlatform {
#endif #endif
auto getRect() -> SDL_FRect; auto getRect() -> SDL_FRect;
auto getCollider() -> SDL_FRect&; auto getCollider() -> SDL_FRect& { return aabb_; }
[[nodiscard]] auto getLastDX() const -> float { return last_dx_; }
[[nodiscard]] auto getLastDY() const -> float { return last_dy_; }
private: private:
void advanceSegment(); void advanceSegment();
@@ -57,9 +56,6 @@ class MovingPlatform {
static auto resolveEasing(const std::string& name) -> EasingFunc; static auto resolveEasing(const std::string& name) -> EasingFunc;
std::shared_ptr<AnimatedSprite> sprite_; std::shared_ptr<AnimatedSprite> sprite_;
SDL_FRect collider_{};
float last_dx_{0.0F};
float last_dy_{0.0F};
// Estado del path // Estado del path
std::vector<Waypoint> path_; std::vector<Waypoint> path_;

View File

@@ -9,7 +9,9 @@
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite #include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
#include "core/resources/resource_cache.hpp" // Para Resource #include "core/resources/resource_cache.hpp" // Para Resource
#include "game/defaults.hpp" // Para Defaults::Game::Player, Defaults::Sound::Files #include "game/defaults.hpp" // Para Defaults::Game::Player, Defaults::Sound::Files
#include "game/entities/solid_actor.hpp" // Para SolidActor
#include "game/gameplay/room.hpp" // Para Room #include "game/gameplay/room.hpp" // Para Room
#include "game/gameplay/solid_actor_manager.hpp" // Para SolidActorManager
#include "game/gameplay/tile_collider.hpp" // Para TileCollider #include "game/gameplay/tile_collider.hpp" // Para TileCollider
#include "game/options.hpp" // Para Cheat, Options #include "game/options.hpp" // Para Cheat, Options
#include "utils/defines.hpp" // Para PlayArea, Collision #include "utils/defines.hpp" // Para PlayArea, Collision
@@ -18,6 +20,29 @@
#include "core/system/debug.hpp" // Para Debug #include "core/system/debug.hpp" // Para Debug
#endif #endif
// Helpers NONE-aware para combinar un hit del TileCollider con un hit del
// SolidActorManager. Devuelven el valor más "clampante" (más a la derecha
// para left-wall, más a la izquierda para right-wall, más abajo para ceiling).
namespace {
auto combineLeftWall(float tile_hit, float actor_hit) -> float {
if (tile_hit == Collision::NONE) { return actor_hit; }
if (actor_hit == Collision::NONE) { return tile_hit; }
return std::max(tile_hit, actor_hit);
}
auto combineRightWall(float tile_hit, float actor_hit) -> float {
if (tile_hit == Collision::NONE) { return actor_hit; }
if (actor_hit == Collision::NONE) { return tile_hit; }
return std::min(tile_hit, actor_hit);
}
auto combineCeiling(float tile_hit, float actor_hit) -> float {
if (tile_hit == Collision::NONE) { return actor_hit; }
if (actor_hit == Collision::NONE) { return tile_hit; }
return std::max(tile_hit, actor_hit);
}
} // namespace
// ============================================================================ // ============================================================================
// Constructor // Constructor
// ============================================================================ // ============================================================================
@@ -46,6 +71,16 @@ void Player::render() {
void Player::update(float delta_time) { void Player::update(float delta_time) {
if (is_paused_) { return; } if (is_paused_) { return; }
// 0. Carry de plataforma móvil (SolidActor con CARRY_ON_TOP).
// Snap absoluto del eje Y al top del AABB y desplazamiento horizontal
// por el delta del último frame del actor. Esto mantiene al Player
// pegado a la plataforma cuando sube/baja y lo arrastra lateralmente.
if (current_carrier_ != nullptr) {
const auto& aabb = current_carrier_->getAABB();
x_ += current_carrier_->getLastDelta().x;
y_ = aabb.y - HEIGHT;
}
// 1. Leer input // 1. Leer input
handleInput(); handleInput();
@@ -236,34 +271,48 @@ void Player::startJump() {
// Fase 4a: Movimiento horizontal // Fase 4a: Movimiento horizontal
// ============================================================================ // ============================================================================
void Player::moveHorizontal(float delta_time) { // Early exit del movimiento horizontal si el player ya está pegado a una
// pared en su dirección de movimiento. Sin esto, el player choca pero
// conserva vx_ != 0 y animate() reproduce "walk" continuamente.
auto Player::stuckAgainstWall() const -> bool {
const auto& tc = room_->getTileCollider(); const auto& tc = room_->getTileCollider();
const auto& sm = room_->getSolidActors();
// Early exit: si hay pared inmediata en la dirección de movimiento, parar if (vx_ > 0.0F) {
// y poner vx_=0. Sin esto, el player choca, queda re-posicionado en el return tc.checkWallRight(x_, y_, WIDTH, HEIGHT) != Collision::NONE ||
// mismo sitio pero conserva vx_ != 0, así que animate() reproduce walk sm.checkWallRight(x_, y_, WIDTH, HEIGHT) != Collision::NONE;
// anim continuamente mientras empuja contra la pared.
if (vx_ > 0.0F && tc.checkWallRight(x_, y_, WIDTH, HEIGHT) != Collision::NONE) {
vx_ = 0.0F;
return;
} }
if (vx_ < 0.0F && tc.checkWallLeft(x_, y_, WIDTH, HEIGHT) != Collision::NONE) { if (vx_ < 0.0F) {
return tc.checkWallLeft(x_, y_, WIDTH, HEIGHT) != Collision::NONE ||
sm.checkWallLeft(x_, y_, WIDTH, HEIGHT) != Collision::NONE;
}
return false;
}
void Player::moveHorizontal(float delta_time) {
if (stuckAgainstWall()) {
vx_ = 0.0F; vx_ = 0.0F;
return; return;
} }
const auto& tc = room_->getTileCollider();
const auto& sm = room_->getSolidActors();
float new_x = x_ + (vx_ * delta_time); float new_x = x_ + (vx_ * delta_time);
// Comprobar ambos muros siempre (el tilemap extendido incluye paredes de rooms // Comprobar ambos muros siempre (el tilemap extendido incluye paredes de rooms
// adyacentes; comprobar ambos lados evita solapamiento en zona de borde) // adyacentes; comprobar ambos lados evita solapamiento en zona de borde).
float wall = tc.checkWallLeft(new_x, y_, WIDTH, HEIGHT); // Se combinan tiles + solid actors en cada lado tomando el muro más "clampante".
if (wall != Collision::NONE && wall > new_x) { const float LEFT_WALL = combineLeftWall(
new_x = wall; tc.checkWallLeft(new_x, y_, WIDTH, HEIGHT),
sm.checkWallLeft(new_x, y_, WIDTH, HEIGHT));
if (LEFT_WALL != Collision::NONE && LEFT_WALL > new_x) {
new_x = LEFT_WALL;
} }
wall = tc.checkWallRight(new_x, y_, WIDTH, HEIGHT); const float RIGHT_WALL = combineRightWall(
if (wall != Collision::NONE) { tc.checkWallRight(new_x, y_, WIDTH, HEIGHT),
float corrected = wall - WIDTH; sm.checkWallRight(new_x, y_, WIDTH, HEIGHT));
if (corrected < new_x) { new_x = corrected; } if (RIGHT_WALL != Collision::NONE) {
const float CORRECTED = RIGHT_WALL - WIDTH;
new_x = std::min(new_x, CORRECTED);
} }
x_ = new_x; x_ = new_x;
@@ -355,44 +404,74 @@ void Player::detectSlopeEntry() {
// Fase 4b: Movimiento vertical // Fase 4b: Movimiento vertical
// ============================================================================ // ============================================================================
void Player::moveVertical(float delta_time) { // Subiendo: comprobar techo (tiles + solid actors). Si hay colisión,
if (state_ != State::ON_AIR) { return; } // snap y parar vy.
void Player::moveVerticalUp(float displacement) {
const auto& tc = room_->getTileCollider(); const auto& tc = room_->getTileCollider();
float displacement = vy_ * delta_time; const auto& sm = room_->getSolidActors();
const float NEW_Y = y_ + displacement;
if (vy_ < 0.0F) { const float CEILING = combineCeiling(
// Subiendo: comprobar techo tc.checkCeiling(x_, NEW_Y, WIDTH),
float new_y = y_ + displacement; sm.checkCeiling(x_, NEW_Y, WIDTH));
float ceiling = tc.checkCeiling(x_, new_y, WIDTH); if (CEILING != Collision::NONE) {
if (ceiling != Collision::NONE) { y_ = CEILING;
y_ = ceiling;
vy_ = 0.0F; vy_ = 0.0F;
} else { } else {
y_ = new_y; y_ = NEW_Y;
}
} }
} else if (vy_ > 0.0F) {
// Bajando: comprobar suelo
float foot_y = y_ + HEIGHT;
float new_foot_y = foot_y + displacement;
auto hit = tc.checkFloor(x_, foot_y, WIDTH, new_foot_y);
if (hit.y != Collision::NONE) { // Bajando: comprobar suelo en tiles y en solid actors; el que esté antes
y_ = hit.y - HEIGHT; // (menor y) gana. Si es un SolidActor con CARRY_ON_TOP, guarda el carrier.
if (hit.type == TileCollider::Tile::SLOPE_L || hit.type == TileCollider::Tile::SLOPE_R) { void Player::moveVerticalDown(float displacement) {
slope_tile_x_ = hit.tile_x; const auto& tc = room_->getTileCollider();
slope_tile_y_ = hit.tile_y; const auto& sm = room_->getSolidActors();
slope_type_ = hit.type; const float FOOT_Y = y_ + HEIGHT;
const float NEW_FOOT_Y = FOOT_Y + displacement;
const auto TILE_HIT = tc.checkFloor(x_, FOOT_Y, WIDTH, NEW_FOOT_Y);
const auto ACTOR_HIT = sm.checkFloor(x_, FOOT_Y, WIDTH, NEW_FOOT_Y);
// El tile tiene prioridad si está igual o más arriba que el actor.
const bool TILE_WINS = (TILE_HIT.y != Collision::NONE) &&
(ACTOR_HIT.y == Collision::NONE || TILE_HIT.y <= ACTOR_HIT.y);
const bool ACTOR_WINS = !TILE_WINS && (ACTOR_HIT.y != Collision::NONE);
if (TILE_WINS) {
y_ = TILE_HIT.y - HEIGHT;
const bool IS_SLOPE = TILE_HIT.type == TileCollider::Tile::SLOPE_L ||
TILE_HIT.type == TileCollider::Tile::SLOPE_R;
if (IS_SLOPE) {
slope_tile_x_ = TILE_HIT.tile_x;
slope_tile_y_ = TILE_HIT.tile_y;
slope_type_ = TILE_HIT.type;
transitionToState(State::ON_SLOPE); transitionToState(State::ON_SLOPE);
} else { } else {
transitionToState(State::ON_GROUND); transitionToState(State::ON_GROUND);
} }
} else { current_carrier_ = nullptr;
return;
}
if (ACTOR_WINS) {
y_ = ACTOR_HIT.y - HEIGHT;
transitionToState(State::ON_GROUND);
current_carrier_ = ACTOR_HIT.carrier;
return;
}
// Cae libremente
y_ += displacement; y_ += displacement;
#ifdef _DEBUG #ifdef _DEBUG
if (y_ > PlayArea::BOTTOM + 100) { y_ = PlayArea::TOP + 2; } if (y_ > PlayArea::BOTTOM + 100) { y_ = PlayArea::TOP + 2; }
#endif #endif
} }
void Player::moveVertical(float delta_time) {
if (state_ != State::ON_AIR) { return; }
const float DISPLACEMENT = vy_ * delta_time;
if (vy_ < 0.0F) {
moveVerticalUp(DISPLACEMENT);
} else if (vy_ > 0.0F) {
moveVerticalDown(DISPLACEMENT);
} }
} }
@@ -404,6 +483,7 @@ void Player::checkFalling() {
if (state_ == State::ON_AIR) { return; } if (state_ == State::ON_AIR) { return; }
const auto& tc = room_->getTileCollider(); const auto& tc = room_->getTileCollider();
const auto& sm = room_->getSolidActors();
if (state_ == State::ON_SLOPE) { if (state_ == State::ON_SLOPE) {
// Verificar que el tile de slope sigue existiendo // Verificar que el tile de slope sigue existiendo
@@ -415,13 +495,14 @@ void Player::checkFalling() {
return; return;
} }
// ON_GROUND: si está sobre una plataforma móvil, no comprobar tiles // ON_GROUND: comprobar si sigue habiendo suelo (tile o solid actor).
if (on_platform_) { return; } // El tilemap extendido incluye tiles de las rooms adyacentes, así que
// no hace falta cross-room para tiles.
// ON_GROUND: comprobar si sigue habiendo suelo (el tilemap extendido
// incluye tiles de las rooms adyacentes, así que no hace falta cross-room)
float foot_y = y_ + HEIGHT; float foot_y = y_ + HEIGHT;
if (!tc.hasGroundBelow(x_, foot_y, WIDTH)) { const bool TILE_GROUND = tc.hasGroundBelow(x_, foot_y, WIDTH);
const bool ACTOR_GROUND = sm.hasGroundBelow(x_, foot_y, WIDTH);
if (!TILE_GROUND && !ACTOR_GROUND) {
// Sticking: si no hay suelo pero hay slope debajo, snapear a ella // Sticking: si no hay suelo pero hay slope debajo, snapear a ella
// para transición suave suelo→slope (bajada de rampas sin caer) // para transición suave suelo→slope (bajada de rampas sin caer)
auto slope = tc.checkSlopeBelow(x_, foot_y, WIDTH); auto slope = tc.checkSlopeBelow(x_, foot_y, WIDTH);
@@ -436,6 +517,16 @@ void Player::checkFalling() {
vy_ = 0.0F; vy_ = 0.0F;
transitionToState(State::ON_AIR); transitionToState(State::ON_AIR);
return;
}
// Refrescar current_carrier_ para la próxima iteración: si hay actor
// debajo, comprobar si lleva CARRY_ON_TOP y guardarlo; si no, limpiarlo.
if (ACTOR_GROUND) {
auto hit = sm.checkFloor(x_, foot_y, WIDTH, foot_y + 1.0F);
current_carrier_ = hit.carrier;
} else {
current_carrier_ = nullptr;
} }
} }
@@ -467,6 +558,7 @@ void Player::transitionToState(State state) {
break; break;
case State::ON_AIR: case State::ON_AIR:
last_grounded_position_ = static_cast<int>(y_); last_grounded_position_ = static_cast<int>(y_);
current_carrier_ = nullptr; // Perder carry al despegar
break; break;
} }
} }
@@ -518,18 +610,6 @@ void Player::syncSpriteAndCollider() {
collider_box_ = getRect(); collider_box_ = getRect();
} }
// Aplica el desplazamiento de una plataforma móvil al jugador
void Player::applyPlatformDisplacement(float dx, float surface_y) {
y_ = surface_y - HEIGHT; // Snap vertical al top de la plataforma
x_ += dx; // Desplazamiento horizontal
vy_ = 0.0F;
on_platform_ = true;
if (state_ != State::ON_GROUND) {
transitionToState(State::ON_GROUND);
}
syncSpriteAndCollider();
}
void Player::placeSprite() { void Player::placeSprite() {
sprite_->setPos(x_, y_); sprite_->setPos(x_, y_);
} }

View File

@@ -12,6 +12,7 @@
#include "game/options.hpp" // Para Cheat, Options #include "game/options.hpp" // Para Cheat, Options
#include "utils/defines.hpp" // Para PlayArea, Tile, Flip #include "utils/defines.hpp" // Para PlayArea, Tile, Flip
struct JA_Sound_t; struct JA_Sound_t;
class SolidActor;
class Player { class Player {
public: public:
@@ -72,8 +73,6 @@ class Player {
void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); } void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); }
[[nodiscard]] auto isAlive() const -> bool { return is_alive_; } [[nodiscard]] auto isAlive() const -> bool { return is_alive_; }
[[nodiscard]] auto getVY() const -> float { return vy_; } [[nodiscard]] auto getVY() const -> float { return vy_; }
void applyPlatformDisplacement(float dx, float surface_y);
void clearPlatformFlag() { on_platform_ = false; }
void setPaused(bool value) { is_paused_ = value; } void setPaused(bool value) { is_paused_ = value; }
void setIgnoreInput(bool value) { ignore_input_ = value; } void setIgnoreInput(bool value) { ignore_input_ = value; }
@@ -119,7 +118,7 @@ class Player {
bool is_alive_ = true; bool is_alive_ = true;
bool is_paused_ = false; bool is_paused_ = false;
bool ignore_input_ = false; bool ignore_input_ = false;
bool on_platform_ = false; SolidActor* current_carrier_ = nullptr; // Actor con CARRY_ON_TOP sobre el que estamos de pie
bool turning_ = false; bool turning_ = false;
Direction facing_ = Direction::RIGHT; Direction facing_ = Direction::RIGHT;
Room::Border border_ = Room::Border::TOP; Room::Border border_ = Room::Border::TOP;
@@ -134,8 +133,11 @@ class Player {
void updateVelocity(float delta_time); void updateVelocity(float delta_time);
void applyGravity(float delta_time); void applyGravity(float delta_time);
void handleJumpAndDrop(); void handleJumpAndDrop();
[[nodiscard]] auto stuckAgainstWall() const -> bool;
void moveHorizontal(float delta_time); void moveHorizontal(float delta_time);
void moveVertical(float delta_time); void moveVertical(float delta_time);
void moveVerticalUp(float displacement);
void moveVerticalDown(float displacement);
void followSlope(); void followSlope();
void exitSlope(); void exitSlope();
void detectSlopeEntry(); void detectSlopeEntry();

View File

@@ -0,0 +1,53 @@
#pragma once
#include <SDL3/SDL.h>
#include <cstdint>
/**
* @brief Clase base ligera para entidades con AABB dinámico de colisión.
*
* Un SolidActor expone un rectángulo (aabb_) al SolidActorManager, que lo
* usa para los sweeps del Player y otras queries. No es polimórfica: no
* tiene métodos virtuales (las entidades concretas viven en sus propios
* managers, que las actualizan y las renderizan). La base solo unifica
* el "bit de colisión" — AABB, flags y delta del último frame.
*
* Hereda de esta clase cualquier entidad que necesite participar en la
* resolución de colisión del Player: puertas, plataformas móviles,
* bloques empujables, ascensores, compuertas, etc.
*
* Las flags determinan el comportamiento del actor ante el sweep:
* - BLOCKS_PLAYER: el AABB bloquea al Player como un muro en las 4 dirs.
* - CARRY_ON_TOP: si el Player está de pie encima, aplica last_delta_.x.
* - ONEWAY_TOP: solo bloquea desde arriba (jump-through desde abajo).
* - KILLS_ON_CRUSH: (futuro) si aplasta al Player contra tile sólido, mata.
*/
class SolidActor {
public:
enum Flags : uint32_t {
BLOCKS_PLAYER = 1U << 0U,
CARRY_ON_TOP = 1U << 1U,
ONEWAY_TOP = 1U << 2U,
KILLS_ON_CRUSH = 1U << 3U,
};
SolidActor() = default;
SolidActor(const SolidActor&) = delete;
auto operator=(const SolidActor&) -> SolidActor& = delete;
SolidActor(SolidActor&&) = delete;
auto operator=(SolidActor&&) -> SolidActor& = delete;
~SolidActor() = default;
[[nodiscard]] auto getAABB() const -> const SDL_FRect& { return aabb_; }
[[nodiscard]] auto getFlags() const -> uint32_t { return flags_; }
[[nodiscard]] auto getLastDelta() const -> SDL_FPoint { return last_delta_; }
[[nodiscard]] auto isBlocking() const -> bool { return (flags_ & BLOCKS_PLAYER) != 0U; }
[[nodiscard]] auto carriesOnTop() const -> bool { return (flags_ & CARRY_ON_TOP) != 0U; }
[[nodiscard]] auto isOneWayTop() const -> bool { return (flags_ & ONEWAY_TOP) != 0U; }
protected:
SDL_FRect aabb_{}; // Rectángulo de colisión (room-local pixel space)
SDL_FPoint last_delta_{}; // (dx, dy) del último frame — para carry horizontal
uint32_t flags_{0};
};

View File

@@ -3,31 +3,30 @@
#include <utility> // Para std::move #include <utility> // Para std::move
#include "game/entities/door.hpp" // Para Door #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/door_tracker.hpp" // Para DoorTracker
#include "game/gameplay/inventory.hpp" // Para Inventory #include "game/gameplay/inventory.hpp" // Para Inventory
#include "game/gameplay/tile_collider.hpp" // Para TileCollider::Tile #include "game/gameplay/solid_actor_manager.hpp" // Para SolidActorManager
#include "utils/defines.hpp" // Para Map::WIDTH, Tile::SIZE
#include "utils/utils.hpp" // Para checkCollision #include "utils/utils.hpp" // Para checkCollision
// Constructor // 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)), : 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 // Añade una puerta y la registra en el SolidActorManager. El bit
// en el CollisionMap. Si ya está OPENED (porque venía persistida del // BLOCKS_PLAYER del propio Door determina si bloquea al Player (se setea en
// DoorTracker), no se tocan los tiles. // el constructor si la puerta no arranca ya abierta desde el DoorTracker).
void DoorManager::addDoor(std::shared_ptr<Door> door) { // NOLINT(readability-identifier-naming) void DoorManager::addDoor(std::shared_ptr<Door> door) { // NOLINT(readability-identifier-naming)
if (door->isBlocking()) { solid_actors_->registerActor(door.get());
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::WALL));
}
doors_.push_back(std::move(door)); doors_.push_back(std::move(door));
} }
// Elimina todas las puertas // Elimina todas las puertas y las desregistra del SolidActorManager
void DoorManager::clear() { void DoorManager::clear() {
for (const auto& door : doors_) {
solid_actors_->unregisterActor(door.get());
}
doors_.clear(); doors_.clear();
} }
@@ -36,9 +35,10 @@ void DoorManager::update(float delta_time) {
for (const auto& door : doors_) { for (const auto& door : doors_) {
door->update(delta_time); 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()) { if (door->justOpened()) {
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::EMPTY));
DoorTracker::get()->addDoor(room_id_, door->getPos()); DoorTracker::get()->addDoor(room_id_, door->getPos());
} }
} }
@@ -79,48 +79,17 @@ void DoorManager::tryUnlock(const SDL_FRect& player_rect) {
} }
#ifdef _DEBUG #ifdef _DEBUG
// Mueve una puerta del editor: limpia los WALLs viejos, reposiciona el sprite, // Mueve una puerta del editor: reposiciona su sprite y AABB. El registro
// y reescribe los WALLs nuevos si la puerta sigue siendo bloqueante. // en el SolidActorManager se mantiene (el manager lee el AABB actual).
void DoorManager::moveDoor(int index, float x, float y) { void DoorManager::moveDoor(int index, float x, float y) {
if (index < 0 || index >= static_cast<int>(doors_.size())) { return; } if (index < 0 || index >= static_cast<int>(doors_.size())) { return; }
auto& door = doors_[index]; doors_[index]->setPosition(x, y);
// 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 // Elimina una puerta del editor, desregistrándola del SolidActorManager
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));
}
}
// Elimina una puerta del editor, limpiando los WALLs antes de borrarla del vector
void DoorManager::removeDoor(int index) { void DoorManager::removeDoor(int index) {
if (index < 0 || index >= static_cast<int>(doors_.size())) { return; } if (index < 0 || index >= static_cast<int>(doors_.size())) { return; }
auto& door = doors_[index]; solid_actors_->unregisterActor(doors_[index].get());
if (door->isBlocking()) {
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::EMPTY));
}
doors_.erase(doors_.begin() + index); doors_.erase(doors_.begin() + index);
} }
#endif #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 #include "game/entities/door.hpp" // Para Door, Door::Data
class CollisionMap; class SolidActorManager;
/** /**
* @brief Gestor de puertas de una habitación * @brief Gestor de puertas de una habitación
@@ -18,14 +18,14 @@ class CollisionMap;
* - Actualizar y renderizar todas las puertas * - Actualizar y renderizar todas las puertas
* - Detectar contacto del jugador con puertas cerradas y disparar la apertura * - Detectar contacto del jugador con puertas cerradas y disparar la apertura
* si tiene la llave correspondiente (consultando el Inventory global) * si tiene la llave correspondiente (consultando el Inventory global)
* - Sincronizar el estado bloqueante con el CollisionMap: cuando una puerta * - Registrar cada puerta en el SolidActorManager para que sus AABBs
* está CLOSED u OPENING, sus 4 tiles son WALL; cuando pasa a OPENED, se * participen en los sweeps de colisión del Player. El bit BLOCKS_PLAYER
* ponen a EMPTY * del propio Door se encarga de activar/desactivar el bloqueo al abrir.
* - Persistir el estado abierto en DoorTracker * - Persistir el estado abierto en DoorTracker
*/ */
class DoorManager { class DoorManager {
public: public:
DoorManager(std::string room_id, CollisionMap* collision_map); DoorManager(std::string room_id, SolidActorManager* solid_actors);
~DoorManager() = default; ~DoorManager() = default;
// Prohibir copia y movimiento para evitar duplicación accidental // Prohibir copia y movimiento para evitar duplicación accidental
@@ -35,7 +35,7 @@ class DoorManager {
auto operator=(DoorManager&&) -> DoorManager& = delete; auto operator=(DoorManager&&) -> DoorManager& = delete;
// Gestión de puertas // 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 void clear(); // Elimina todas las puertas
// Actualización y renderizado // Actualización y renderizado
@@ -66,24 +66,20 @@ class DoorManager {
/** /**
* @brief Mueve la puerta indicada a (x, y) en píxeles * @brief Mueve la puerta indicada a (x, y) en píxeles
* *
* Limpia los WALLs viejos del CollisionMap y, si la puerta sigue siendo * Reposiciona el sprite y el AABB de la puerta. El registro en el
* bloqueante, escribe los nuevos. Encapsula el bookkeeping de tiles para * SolidActorManager no cambia: el manager siempre lee el AABB actual
* que el editor nunca toque el CollisionMap directamente. * del SolidActor durante los sweeps.
*/ */
void moveDoor(int index, float x, float y); 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); void removeDoor(int index);
#endif #endif
private: 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::vector<std::shared_ptr<Door>> doors_; // Colección de puertas
std::string room_id_; // Identificador de la habitación 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 "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) { void PlatformManager::addPlatform(std::shared_ptr<MovingPlatform> platform) {
solid_actors_->registerActor(platform.get());
platforms_.push_back(std::move(platform)); platforms_.push_back(std::move(platform));
} }
// Elimina todas las plataformas // Elimina todas las plataformas y las desregistra del SolidActorManager
void PlatformManager::clear() { void PlatformManager::clear() {
for (const auto& platform : platforms_) {
solid_actors_->unregisterActor(platform.get());
}
platforms_.clear(); platforms_.clear();
} }
@@ -57,40 +69,11 @@ auto PlatformManager::getPlatform(int index) -> std::shared_ptr<MovingPlatform>&
return platforms_.at(index); return platforms_.at(index);
} }
// Elimina la última plataforma // Elimina la última plataforma (y la desregistra del SolidActorManager)
void PlatformManager::removeLastPlatform() { void PlatformManager::removeLastPlatform() {
if (!platforms_.empty()) { if (!platforms_.empty()) {
solid_actors_->unregisterActor(platforms_.back().get());
platforms_.pop_back(); platforms_.pop_back();
} }
} }
#endif #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 #include "game/entities/moving_platform.hpp" // Para MovingPlatform, MovingPlatform::Data
class SolidActorManager;
class PlatformManager { class PlatformManager {
public: public:
PlatformManager() = default; explicit PlatformManager(SolidActorManager* solid_actors);
~PlatformManager() = default; ~PlatformManager() = default;
// Prohibir copia y movimiento // Prohibir copia y movimiento
@@ -27,10 +29,6 @@ class PlatformManager {
void update(float delta_time); void update(float delta_time);
void render(); 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 #ifdef _DEBUG
void updateAnimations(float delta_time); void updateAnimations(float delta_time);
void resetPositions(const std::vector<MovingPlatform::Data>& platform_data); void resetPositions(const std::vector<MovingPlatform::Data>& platform_data);
@@ -41,4 +39,5 @@ class PlatformManager {
private: private:
std::vector<std::shared_ptr<MovingPlatform>> platforms_; std::vector<std::shared_ptr<MovingPlatform>> platforms_;
SolidActorManager* solid_actors_{nullptr}; // Referencia no propietaria al SolidActorManager de la Room
}; };

View File

@@ -16,6 +16,7 @@
#include "game/gameplay/platform_manager.hpp" // Para PlatformManager #include "game/gameplay/platform_manager.hpp" // Para PlatformManager
#include "game/gameplay/room_format.hpp" // Para RoomFormat #include "game/gameplay/room_format.hpp" // Para RoomFormat
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data #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 "game/gameplay/tilemap_renderer.hpp" // Para TilemapRenderer
#include "utils/defines.hpp" // Para TILE_SIZE #include "utils/defines.hpp" // Para TILE_SIZE
#include "utils/utils.hpp" #include "utils/utils.hpp"
@@ -25,18 +26,24 @@ Room::Room(const std::string& room_path, std::shared_ptr<Scoreboard::Data> data)
: data_(std::move(data)) { : data_(std::move(data)) {
auto room = Resource::Cache::get()->getRoom(room_path); 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 // Crea los managers de enemigos, items, plataformas y llaves
enemy_manager_ = std::make_unique<EnemyManager>(); enemy_manager_ = std::make_unique<EnemyManager>();
item_manager_ = std::make_unique<ItemManager>(room->number, data_); 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); key_manager_ = std::make_unique<KeyManager>(room->number);
// Crea el mapa de colisiones desde el collision_tile_map (debe ir antes // Crea el mapa de colisiones desde el collision_tile_map
// del DoorManager porque éste lo necesita para mutar tiles dinámicamente)
collision_map_ = std::make_unique<CollisionMap>(room->collision_tile_map); collision_map_ = std::make_unique<CollisionMap>(room->collision_tile_map);
// Crea el manager de puertas (necesita el CollisionMap para sincronizar muros) // Crea el manager de puertas (registra los Door como SolidActor en el
door_manager_ = std::make_unique<DoorManager>(room->number, collision_map_.get()); // SolidActorManager; ya no escribe tiles en el CollisionMap).
door_manager_ = std::make_unique<DoorManager>(room->number, solid_actor_manager_.get());
initializeRoom(*room); initializeRoom(*room);
@@ -250,6 +257,14 @@ auto Room::getTileCollider() const -> const TileCollider& {
return collision_map_->getTileCollider(); 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>& { auto Room::getCollisionTileMap() const -> const std::vector<int>& {
return collision_map_->getCollisionTileMap(); return collision_map_->getCollisionTileMap();
} }
@@ -258,6 +273,10 @@ void Room::updateCollisionBorders(const CollisionMap::AdjacentData& adjacent) {
collision_map_->updateBorders(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 // 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) auto Room::getRoom(Border border) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
switch (border) { switch (border) {
@@ -290,10 +309,6 @@ void Room::tryUnlockDoors(const SDL_FRect& player_rect) {
door_manager_->tryUnlock(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) // 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) auto Room::loadYAML(const std::string& file_path, bool verbose) -> Data { // NOLINT(readability-convert-member-functions-to-static)
return RoomFormat::loadYAML(file_path, verbose); return RoomFormat::loadYAML(file_path, verbose);

View File

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

View File

@@ -319,9 +319,6 @@ void Game::updatePlaying(float delta_time) {
updateAdjacentRooms(delta_time); updateAdjacentRooms(delta_time);
switch (mode_) { switch (mode_) {
case Mode::GAME: case Mode::GAME:
// Plataformas: resetear flag y detectar antes de la física del player
player_->clearPlatformFlag();
checkPlayerAndPlatforms();
#ifdef _DEBUG #ifdef _DEBUG
// Maneja el arrastre del jugador con el ratón (debug) // Maneja el arrastre del jugador con el ratón (debug)
@@ -858,6 +855,24 @@ void Game::buildCollisionBorders() {
adj.bottom_right = getDiagCollision(Room::Border::BOTTOM, Room::Border::RIGHT); adj.bottom_right = getDiagCollision(Room::Border::BOTTOM, Room::Border::RIGHT);
room_->updateCollisionBorders(adj); room_->updateCollisionBorders(adj);
// Además del tilemap extendido, propagar también los punteros a los
// SolidActorManager de las rooms cardinales adyacentes. Esto permite
// que los sweeps del Player vean AABBs dinámicos (puertas, plataformas)
// de la room vecina cuando está cerca del borde, sin tener que esperar
// a una transición completa de room.
auto getAdjacentSolidActors = [&](Room::Border b) -> SolidActorManager* {
auto name = room_->getRoom(b);
if (name == "0") { return nullptr; }
return &getOrCreateRoom(name)->getSolidActors();
};
SolidActorManager::AdjacentActors sadj;
sadj.upper = getAdjacentSolidActors(Room::Border::TOP);
sadj.lower = getAdjacentSolidActors(Room::Border::BOTTOM);
sadj.left = getAdjacentSolidActors(Room::Border::LEFT);
sadj.right = getAdjacentSolidActors(Room::Border::RIGHT);
room_->updateSolidActorBorders(sadj);
} }
// Actualiza los enemigos de las habitaciones adyacentes a la actual // Actualiza los enemigos de las habitaciones adyacentes a la actual
@@ -950,14 +965,6 @@ auto Game::checkPlayerAndEnemies() -> bool {
return DEATH; return DEATH;
} }
// Comprueba si el jugador está sobre una plataforma móvil y lo transporta
void Game::checkPlayerAndPlatforms() {
auto* platform = room_->checkPlayerOnPlatform(player_->getCollider(), player_->getVY());
if (platform != nullptr) {
player_->applyPlatformDisplacement(platform->getLastDX(), platform->getCollider().y);
}
}
// Comprueba las colisiones del jugador con los objetos // Comprueba las colisiones del jugador con los objetos
void Game::checkPlayerAndKeys() { void Game::checkPlayerAndKeys() {
room_->keyCollision(player_->getCollider()); room_->keyCollision(player_->getCollider());

View File

@@ -75,7 +75,6 @@ class Game {
void handleInput(); // Comprueba el teclado void handleInput(); // Comprueba el teclado
void checkPlayerIsOnBorder(); // Comprueba si el jugador esta en el borde de la pantalla y actua void checkPlayerIsOnBorder(); // Comprueba si el jugador esta en el borde de la pantalla y actua
auto checkPlayerAndEnemies() -> bool; // Comprueba las colisiones del jugador con los enemigos auto checkPlayerAndEnemies() -> bool; // Comprueba las colisiones del jugador con los enemigos
void checkPlayerAndPlatforms(); // Comprueba si el jugador está sobre una plataforma móvil
void checkPlayerAndItems(); // Comprueba las colisiones del jugador con los objetos void checkPlayerAndItems(); // Comprueba las colisiones del jugador con los objetos
void checkPlayerAndKeys(); // Comprueba las colisiones del jugador con las llaves void checkPlayerAndKeys(); // Comprueba las colisiones del jugador con las llaves
void checkIfPlayerIsAlive(); // Comprueba si el jugador esta vivo void checkIfPlayerIsAlive(); // Comprueba si el jugador esta vivo