migrades portes i plataformes a solidActor
This commit is contained in:
@@ -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};
|
||||
};
|
||||
Reference in New Issue
Block a user