migrades portes i plataformes a solidActor

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

View File

@@ -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_);
}