migrades portes i plataformes a solidActor
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ tools/pack_resources/pack_tool.exe
|
||||
*.res
|
||||
dist/
|
||||
.claude/settings.local.json
|
||||
_projecte_2026
|
||||
|
||||
@@ -86,6 +86,7 @@ set(APP_SOURCES
|
||||
source/game/gameplay/key_manager.cpp
|
||||
source/game/gameplay/door_manager.cpp
|
||||
source/game/gameplay/platform_manager.cpp
|
||||
source/game/gameplay/solid_actor_manager.cpp
|
||||
source/game/gameplay/item_tracker.cpp
|
||||
source/game/gameplay/key_tracker.cpp
|
||||
source/game/gameplay/door_tracker.cpp
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug (persistencia de render_info en debug.yaml)
|
||||
#endif
|
||||
#include "game/options.hpp" // Para Options, options, OptionsVideo, Section
|
||||
#include "game/scene_manager.hpp" // Para SceneManager
|
||||
#include "game/ui/console.hpp" // Para Console
|
||||
#include "game/ui/notifier.hpp" // Para Notifier, NotificationText
|
||||
#include "utils/utils.hpp" // Para stringInVector
|
||||
#include "game/options.hpp" // Para Options, options, OptionsVideo, Section
|
||||
#include "game/scene_manager.hpp" // Para SceneManager
|
||||
#include "game/ui/console.hpp" // Para Console
|
||||
#include "game/ui/notifier.hpp" // Para Notifier, NotificationText
|
||||
#include "utils/utils.hpp" // Para stringInVector
|
||||
|
||||
namespace GlobalInputs {
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class Debug {
|
||||
void setInitialScene(SceneManager::Scene s) { initial_scene_ = s; } // Establece la escena inicial de debug
|
||||
[[nodiscard]] auto getLazyLoading() const -> bool { return lazy_loading_; } // Indica si el modo lazy de recursos está activo
|
||||
[[nodiscard]] auto getRenderInfoEnabled() const -> bool { return render_info_enabled_; } // Indica si el overlay RenderInfo arranca activo
|
||||
void setRenderInfoEnabled(bool value); // Persiste el estado del overlay RenderInfo en debug.yaml
|
||||
void setRenderInfoEnabled(bool value); // Persiste el estado del overlay RenderInfo en debug.yaml
|
||||
|
||||
private:
|
||||
static Debug* debug; // [SINGLETON] Objeto privado
|
||||
|
||||
@@ -750,16 +750,12 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
break;
|
||||
case EntityType::DOOR:
|
||||
if (IDX >= 0 && IDX < static_cast<int>(room_data_.doors.size())) {
|
||||
// Truco crítico: durante el drag, moveEntityVisual movió el sprite
|
||||
// pero los WALLs del CollisionMap siguen en la posición antigua. Antes
|
||||
// de llamar a moveDoor (que limpia los tiles "actuales" y escribe los
|
||||
// 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);
|
||||
// Con el SolidActorManager, moveDoor solo reposiciona el AABB
|
||||
// del Door (no hay tiles que sincronizar). moveEntityVisual ya
|
||||
// movió el sprite durante el drag; moveDoor hace el ajuste final.
|
||||
room_data_.doors[IDX].x = drag_.snap_x;
|
||||
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};
|
||||
return true;
|
||||
}
|
||||
@@ -2503,11 +2499,12 @@ auto MapEditor::duplicateKey() -> std::string {
|
||||
// (id, animation) requiere recrear el Door y mantener los tiles sincronizados.
|
||||
void MapEditor::rebuildDoors() {
|
||||
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) {
|
||||
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) {
|
||||
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;
|
||||
|
||||
// Importante: usar removeDoor del manager (limpia los WALLs antes de borrar)
|
||||
// Importante: usar removeDoor del manager (desregistra del SolidActorManager)
|
||||
room_->getDoorManager()->removeDoor(IDX);
|
||||
room_data_.doors.erase(room_data_.doors.begin() + IDX);
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
: sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData(data.animation_path))),
|
||||
id_(data.id),
|
||||
@@ -13,7 +15,10 @@ Door::Door(const Data& data, bool start_opened)
|
||||
sprite_->setPosX(data.x);
|
||||
sprite_->setPosY(data.y);
|
||||
sprite_->setCurrentAnimation(start_opened ? "opened" : "closed");
|
||||
collider_ = sprite_->getRect();
|
||||
aabb_ = sprite_->getRect();
|
||||
if (!start_opened) {
|
||||
flags_ = BLOCKS_PLAYER;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// libere los tiles de colisión.
|
||||
// OPENED, fija el frame final, limpia el flag BLOCKS_PLAYER (los sweeps
|
||||
// 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) {
|
||||
if (is_paused_) {
|
||||
return;
|
||||
@@ -35,6 +41,7 @@ void Door::update(float delta_time) {
|
||||
if (sprite_->animationIsCompleted()) {
|
||||
state_ = State::OPENED;
|
||||
sprite_->setCurrentAnimation("opened");
|
||||
flags_ = 0; // Deja de bloquear al Player
|
||||
just_opened_ = true;
|
||||
}
|
||||
}
|
||||
@@ -65,11 +72,12 @@ auto Door::justOpened() -> bool {
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Mueve la puerta a la posición indicada (sprite + collider). NO toca el
|
||||
// CollisionMap — eso es responsabilidad del DoorManager (moveDoor/removeDoor).
|
||||
// Mueve la puerta a la posición indicada (sprite + AABB del SolidActor).
|
||||
// Usado por el editor; el DoorManager::moveDoor se encarga del bookkeeping
|
||||
// de registro en el SolidActorManager.
|
||||
void Door::setPosition(float x, float y) {
|
||||
sprite_->setPosX(x);
|
||||
sprite_->setPosY(y);
|
||||
collider_ = sprite_->getRect();
|
||||
aabb_ = sprite_->getRect();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "game/entities/solid_actor.hpp" // Para SolidActor
|
||||
|
||||
class AnimatedSprite;
|
||||
|
||||
/**
|
||||
@@ -15,12 +17,12 @@ class AnimatedSprite;
|
||||
* - "opening": animación de transición que se reproduce una sola vez
|
||||
* - "opened": estado final no bloqueante (frame estático)
|
||||
*
|
||||
* El comportamiento de "muro" se implementa marcando los 4 tiles que ocupa
|
||||
* la puerta como WALL en el CollisionMap (lo gestiona el DoorManager). Cuando
|
||||
* la puerta termina de abrirse, los tiles vuelven a EMPTY y el jugador puede
|
||||
* pasar.
|
||||
* El comportamiento de "muro" se implementa como SolidActor con flag
|
||||
* BLOCKS_PLAYER. El SolidActorManager de la Room lo consulta en los sweeps
|
||||
* del Player. Cuando la puerta termina de abrirse, se limpia el flag
|
||||
* BLOCKS_PLAYER y el sweep deja de verla como pared.
|
||||
*/
|
||||
class Door {
|
||||
class Door : public SolidActor {
|
||||
public:
|
||||
enum class State : int {
|
||||
CLOSED = 0,
|
||||
@@ -41,11 +43,10 @@ class Door {
|
||||
void render(); // Pinta la puerta en pantalla
|
||||
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)
|
||||
[[nodiscard]] auto getPos() const -> SDL_FPoint; // Posición en píxeles
|
||||
[[nodiscard]] auto getId() const -> const std::string& { return id_; } // Identificador
|
||||
[[nodiscard]] auto getState() const -> State { return state_; } // Estado actual
|
||||
[[nodiscard]] auto isBlocking() const -> bool { return state_ != State::OPENED; } // True si bloquea al jugador
|
||||
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 getId() const -> const std::string& { return id_; } // Identificador
|
||||
[[nodiscard]] auto getState() const -> State { return state_; } // Estado actual
|
||||
|
||||
void startOpening(); // Transición CLOSED → OPENING
|
||||
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
|
||||
|
||||
#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
|
||||
|
||||
private:
|
||||
std::shared_ptr<AnimatedSprite> sprite_; // Sprite animado de la puerta
|
||||
SDL_FRect collider_{}; // Rectángulo de colisión
|
||||
std::string id_; // Identificador
|
||||
State state_{State::CLOSED}; // Estado actual
|
||||
bool just_opened_{false}; // Flag one-shot: la puerta acaba de transicionar a OPENED
|
||||
|
||||
@@ -28,13 +28,19 @@ MovingPlatform::MovingPlatform(const Data& data)
|
||||
speed_(data.speed),
|
||||
loop_mode_(data.loop),
|
||||
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
|
||||
if (!path_.empty()) {
|
||||
sprite_->setPosX(path_[0].x);
|
||||
sprite_->setPosY(path_[0].y);
|
||||
}
|
||||
|
||||
collider_ = getRect();
|
||||
aabb_ = getRect();
|
||||
|
||||
// Frame inicial
|
||||
sprite_->setCurrentAnimationFrame((data.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : data.frame);
|
||||
@@ -115,8 +121,8 @@ void MovingPlatform::update(float delta_time) {
|
||||
sprite_->animate(delta_time);
|
||||
|
||||
if (path_.size() < 2) {
|
||||
last_dx_ = 0.0F;
|
||||
last_dy_ = 0.0F;
|
||||
last_delta_.x = 0.0F;
|
||||
last_delta_.y = 0.0F;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,8 +136,8 @@ void MovingPlatform::update(float delta_time) {
|
||||
waiting_ = false;
|
||||
advanceSegment();
|
||||
} else {
|
||||
last_dx_ = 0.0F;
|
||||
last_dy_ = 0.0F;
|
||||
last_delta_.x = 0.0F;
|
||||
last_delta_.y = 0.0F;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -174,9 +180,9 @@ void MovingPlatform::update(float delta_time) {
|
||||
sprite_->setPosY(new_y);
|
||||
}
|
||||
|
||||
last_dx_ = sprite_->getPosX() - old_x;
|
||||
last_dy_ = sprite_->getPosY() - old_y;
|
||||
collider_ = getRect();
|
||||
last_delta_.x = sprite_->getPosX() - old_x;
|
||||
last_delta_.y = sprite_->getPosY() - old_y;
|
||||
aabb_ = getRect();
|
||||
}
|
||||
|
||||
// Pinta la plataforma en pantalla
|
||||
@@ -208,7 +214,7 @@ void MovingPlatform::resetToInitialPosition(const Data& data) {
|
||||
sprite_->setPosY(path_[0].y);
|
||||
}
|
||||
|
||||
collider_ = getRect();
|
||||
aabb_ = getRect();
|
||||
recalcSegmentLength();
|
||||
}
|
||||
#endif
|
||||
@@ -217,8 +223,3 @@ void MovingPlatform::resetToInitialPosition(const Data& data) {
|
||||
auto MovingPlatform::getRect() -> SDL_FRect {
|
||||
return sprite_->getRect();
|
||||
}
|
||||
|
||||
// Obtiene el rectangulo de colisión
|
||||
auto MovingPlatform::getCollider() -> SDL_FRect& {
|
||||
return collider_;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "game/entities/solid_actor.hpp" // Para SolidActor
|
||||
|
||||
class AnimatedSprite;
|
||||
|
||||
// Punto de paso en la ruta de una plataforma
|
||||
@@ -22,7 +24,7 @@ enum class LoopMode { PINGPONG,
|
||||
// Tipo de función de easing
|
||||
using EasingFunc = float (*)(float);
|
||||
|
||||
class MovingPlatform {
|
||||
class MovingPlatform : public SolidActor {
|
||||
public:
|
||||
struct Data {
|
||||
std::string animation_path;
|
||||
@@ -44,10 +46,7 @@ class MovingPlatform {
|
||||
#endif
|
||||
|
||||
auto getRect() -> SDL_FRect;
|
||||
auto getCollider() -> SDL_FRect&;
|
||||
|
||||
[[nodiscard]] auto getLastDX() const -> float { return last_dx_; }
|
||||
[[nodiscard]] auto getLastDY() const -> float { return last_dy_; }
|
||||
auto getCollider() -> SDL_FRect& { return aabb_; }
|
||||
|
||||
private:
|
||||
void advanceSegment();
|
||||
@@ -57,9 +56,6 @@ class MovingPlatform {
|
||||
static auto resolveEasing(const std::string& name) -> EasingFunc;
|
||||
|
||||
std::shared_ptr<AnimatedSprite> sprite_;
|
||||
SDL_FRect collider_{};
|
||||
float last_dx_{0.0F};
|
||||
float last_dy_{0.0F};
|
||||
|
||||
// Estado del path
|
||||
std::vector<Waypoint> path_;
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||
#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/solid_actor_manager.hpp" // Para SolidActorManager
|
||||
#include "game/gameplay/tile_collider.hpp" // Para TileCollider
|
||||
#include "game/options.hpp" // Para Cheat, Options
|
||||
#include "utils/defines.hpp" // Para PlayArea, Collision
|
||||
@@ -18,6 +20,29 @@
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#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
|
||||
// ============================================================================
|
||||
@@ -46,6 +71,16 @@ void Player::render() {
|
||||
void Player::update(float delta_time) {
|
||||
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
|
||||
handleInput();
|
||||
|
||||
@@ -236,34 +271,48 @@ void Player::startJump() {
|
||||
// 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();
|
||||
|
||||
// Early exit: si hay pared inmediata en la dirección de movimiento, parar
|
||||
// y poner vx_=0. Sin esto, el player choca, queda re-posicionado en el
|
||||
// mismo sitio pero conserva vx_ != 0, así que animate() reproduce walk
|
||||
// anim continuamente mientras empuja contra la pared.
|
||||
if (vx_ > 0.0F && tc.checkWallRight(x_, y_, WIDTH, HEIGHT) != Collision::NONE) {
|
||||
vx_ = 0.0F;
|
||||
return;
|
||||
const auto& sm = room_->getSolidActors();
|
||||
if (vx_ > 0.0F) {
|
||||
return tc.checkWallRight(x_, y_, WIDTH, HEIGHT) != Collision::NONE ||
|
||||
sm.checkWallRight(x_, y_, WIDTH, HEIGHT) != Collision::NONE;
|
||||
}
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& tc = room_->getTileCollider();
|
||||
const auto& sm = room_->getSolidActors();
|
||||
float new_x = x_ + (vx_ * delta_time);
|
||||
|
||||
// Comprobar ambos muros siempre (el tilemap extendido incluye paredes de rooms
|
||||
// adyacentes; comprobar ambos lados evita solapamiento en zona de borde)
|
||||
float wall = tc.checkWallLeft(new_x, y_, WIDTH, HEIGHT);
|
||||
if (wall != Collision::NONE && wall > new_x) {
|
||||
new_x = wall;
|
||||
// adyacentes; comprobar ambos lados evita solapamiento en zona de borde).
|
||||
// Se combinan tiles + solid actors en cada lado tomando el muro más "clampante".
|
||||
const float LEFT_WALL = combineLeftWall(
|
||||
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);
|
||||
if (wall != Collision::NONE) {
|
||||
float corrected = wall - WIDTH;
|
||||
if (corrected < new_x) { new_x = corrected; }
|
||||
const float RIGHT_WALL = combineRightWall(
|
||||
tc.checkWallRight(new_x, y_, WIDTH, HEIGHT),
|
||||
sm.checkWallRight(new_x, y_, WIDTH, HEIGHT));
|
||||
if (RIGHT_WALL != Collision::NONE) {
|
||||
const float CORRECTED = RIGHT_WALL - WIDTH;
|
||||
new_x = std::min(new_x, CORRECTED);
|
||||
}
|
||||
|
||||
x_ = new_x;
|
||||
@@ -355,44 +404,74 @@ void Player::detectSlopeEntry() {
|
||||
// Fase 4b: Movimiento vertical
|
||||
// ============================================================================
|
||||
|
||||
// Subiendo: comprobar techo (tiles + solid actors). Si hay colisión,
|
||||
// snap y parar vy.
|
||||
void Player::moveVerticalUp(float displacement) {
|
||||
const auto& tc = room_->getTileCollider();
|
||||
const auto& sm = room_->getSolidActors();
|
||||
const float NEW_Y = y_ + displacement;
|
||||
const float CEILING = combineCeiling(
|
||||
tc.checkCeiling(x_, NEW_Y, WIDTH),
|
||||
sm.checkCeiling(x_, NEW_Y, WIDTH));
|
||||
if (CEILING != Collision::NONE) {
|
||||
y_ = CEILING;
|
||||
vy_ = 0.0F;
|
||||
} else {
|
||||
y_ = NEW_Y;
|
||||
}
|
||||
}
|
||||
|
||||
// Bajando: comprobar suelo en tiles y en solid actors; el que esté antes
|
||||
// (menor y) gana. Si es un SolidActor con CARRY_ON_TOP, guarda el carrier.
|
||||
void Player::moveVerticalDown(float displacement) {
|
||||
const auto& tc = room_->getTileCollider();
|
||||
const auto& sm = room_->getSolidActors();
|
||||
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);
|
||||
} else {
|
||||
transitionToState(State::ON_GROUND);
|
||||
}
|
||||
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;
|
||||
#ifdef _DEBUG
|
||||
if (y_ > PlayArea::BOTTOM + 100) { y_ = PlayArea::TOP + 2; }
|
||||
#endif
|
||||
}
|
||||
|
||||
void Player::moveVertical(float delta_time) {
|
||||
if (state_ != State::ON_AIR) { return; }
|
||||
|
||||
const auto& tc = room_->getTileCollider();
|
||||
float displacement = vy_ * delta_time;
|
||||
|
||||
const float DISPLACEMENT = vy_ * delta_time;
|
||||
if (vy_ < 0.0F) {
|
||||
// Subiendo: comprobar techo
|
||||
float new_y = y_ + displacement;
|
||||
float ceiling = tc.checkCeiling(x_, new_y, WIDTH);
|
||||
if (ceiling != Collision::NONE) {
|
||||
y_ = ceiling;
|
||||
vy_ = 0.0F;
|
||||
} else {
|
||||
y_ = new_y;
|
||||
}
|
||||
moveVerticalUp(DISPLACEMENT);
|
||||
} 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) {
|
||||
y_ = hit.y - HEIGHT;
|
||||
if (hit.type == TileCollider::Tile::SLOPE_L || hit.type == TileCollider::Tile::SLOPE_R) {
|
||||
slope_tile_x_ = hit.tile_x;
|
||||
slope_tile_y_ = hit.tile_y;
|
||||
slope_type_ = hit.type;
|
||||
transitionToState(State::ON_SLOPE);
|
||||
} else {
|
||||
transitionToState(State::ON_GROUND);
|
||||
}
|
||||
} else {
|
||||
y_ += displacement;
|
||||
#ifdef _DEBUG
|
||||
if (y_ > PlayArea::BOTTOM + 100) { y_ = PlayArea::TOP + 2; }
|
||||
#endif
|
||||
}
|
||||
moveVerticalDown(DISPLACEMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,6 +483,7 @@ void Player::checkFalling() {
|
||||
if (state_ == State::ON_AIR) { return; }
|
||||
|
||||
const auto& tc = room_->getTileCollider();
|
||||
const auto& sm = room_->getSolidActors();
|
||||
|
||||
if (state_ == State::ON_SLOPE) {
|
||||
// Verificar que el tile de slope sigue existiendo
|
||||
@@ -415,13 +495,14 @@ void Player::checkFalling() {
|
||||
return;
|
||||
}
|
||||
|
||||
// ON_GROUND: si está sobre una plataforma móvil, no comprobar tiles
|
||||
if (on_platform_) { return; }
|
||||
|
||||
// ON_GROUND: comprobar si sigue habiendo suelo (el tilemap extendido
|
||||
// incluye tiles de las rooms adyacentes, así que no hace falta cross-room)
|
||||
// ON_GROUND: comprobar si sigue habiendo suelo (tile o solid actor).
|
||||
// El tilemap extendido incluye tiles de las rooms adyacentes, así que
|
||||
// no hace falta cross-room para tiles.
|
||||
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
|
||||
// para transición suave suelo→slope (bajada de rampas sin caer)
|
||||
auto slope = tc.checkSlopeBelow(x_, foot_y, WIDTH);
|
||||
@@ -436,6 +517,16 @@ void Player::checkFalling() {
|
||||
|
||||
vy_ = 0.0F;
|
||||
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;
|
||||
case State::ON_AIR:
|
||||
last_grounded_position_ = static_cast<int>(y_);
|
||||
current_carrier_ = nullptr; // Perder carry al despegar
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -518,18 +610,6 @@ void Player::syncSpriteAndCollider() {
|
||||
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() {
|
||||
sprite_->setPos(x_, y_);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "game/options.hpp" // Para Cheat, Options
|
||||
#include "utils/defines.hpp" // Para PlayArea, Tile, Flip
|
||||
struct JA_Sound_t;
|
||||
class SolidActor;
|
||||
|
||||
class Player {
|
||||
public:
|
||||
@@ -72,8 +73,6 @@ class Player {
|
||||
void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); }
|
||||
[[nodiscard]] auto isAlive() const -> bool { return is_alive_; }
|
||||
[[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 setIgnoreInput(bool value) { ignore_input_ = value; }
|
||||
@@ -119,7 +118,7 @@ class Player {
|
||||
bool is_alive_ = true;
|
||||
bool is_paused_ = 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;
|
||||
Direction facing_ = Direction::RIGHT;
|
||||
Room::Border border_ = Room::Border::TOP;
|
||||
@@ -134,8 +133,11 @@ class Player {
|
||||
void updateVelocity(float delta_time);
|
||||
void applyGravity(float delta_time);
|
||||
void handleJumpAndDrop();
|
||||
[[nodiscard]] auto stuckAgainstWall() const -> bool;
|
||||
void moveHorizontal(float delta_time);
|
||||
void moveVertical(float delta_time);
|
||||
void moveVerticalUp(float displacement);
|
||||
void moveVerticalDown(float displacement);
|
||||
void followSlope();
|
||||
void exitSlope();
|
||||
void detectSlopeEntry();
|
||||
|
||||
53
source/game/entities/solid_actor.hpp
Normal file
53
source/game/entities/solid_actor.hpp
Normal 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};
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
197
source/game/gameplay/solid_actor_manager.cpp
Normal file
197
source/game/gameplay/solid_actor_manager.cpp
Normal 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;
|
||||
}
|
||||
91
source/game/gameplay/solid_actor_manager.hpp
Normal file
91
source/game/gameplay/solid_actor_manager.hpp
Normal 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_{};
|
||||
};
|
||||
@@ -319,9 +319,6 @@ void Game::updatePlaying(float delta_time) {
|
||||
updateAdjacentRooms(delta_time);
|
||||
switch (mode_) {
|
||||
case Mode::GAME:
|
||||
// Plataformas: resetear flag y detectar antes de la física del player
|
||||
player_->clearPlatformFlag();
|
||||
checkPlayerAndPlatforms();
|
||||
|
||||
#ifdef _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);
|
||||
|
||||
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
|
||||
@@ -950,14 +965,6 @@ auto Game::checkPlayerAndEnemies() -> bool {
|
||||
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
|
||||
void Game::checkPlayerAndKeys() {
|
||||
room_->keyCollision(player_->getCollider());
|
||||
|
||||
@@ -75,7 +75,6 @@ class Game {
|
||||
void handleInput(); // Comprueba el teclado
|
||||
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
|
||||
void checkPlayerAndPlatforms(); // Comprueba si el jugador está sobre una plataforma móvil
|
||||
void checkPlayerAndItems(); // Comprueba las colisiones del jugador con los objetos
|
||||
void checkPlayerAndKeys(); // Comprueba las colisiones del jugador con las llaves
|
||||
void checkIfPlayerIsAlive(); // Comprueba si el jugador esta vivo
|
||||
|
||||
Reference in New Issue
Block a user