treballant en el extendedMap per a les colisións fora de pantalla

This commit is contained in:
2026-04-09 21:46:45 +02:00
parent 2120641c3d
commit 4f890586f1
20 changed files with 326 additions and 383 deletions

View File

@@ -66,8 +66,8 @@ void Player::update(float delta_time) {
checkFalling();
// 6. Kill tiles
auto [ktc, kox, koy] = getCollisionContext();
if (ktc.touchesKillTile(x_ + kox, y_ + koy, WIDTH, HEIGHT)) {
const auto& ktc = room_->getTileCollider();
if (ktc.touchesKillTile(x_, y_, WIDTH, HEIGHT)) {
markAsDead();
}
@@ -200,11 +200,11 @@ void Player::handleJumpAndDrop() {
// Drop-through: plataforma passable
if (wanna_down_ && state_ == State::ON_GROUND) {
auto [tc, ox, oy] = getCollisionContext();
float foot_y = (y_ + oy) + HEIGHT;
const auto& tc = room_->getTileCollider();
float foot_y = y_ + HEIGHT;
int foot_row = static_cast<int>(foot_y) / Tile::SIZE;
int left_col = static_cast<int>(x_ + ox) / Tile::SIZE;
int right_col = static_cast<int>((x_ + ox) + WIDTH - 1) / Tile::SIZE;
int left_col = static_cast<int>(x_) / Tile::SIZE;
int right_col = static_cast<int>(x_ + WIDTH - 1) / Tile::SIZE;
for (int col = left_col; col <= right_col; ++col) {
if (tc.getTileAt(col, foot_row) == TileCollider::Tile::PASSABLE) {
@@ -230,43 +230,27 @@ void Player::startJump() {
// ============================================================================
void Player::moveHorizontal(float delta_time) {
if (vx_ == 0.0F) {
// Aunque no haya movimiento horizontal, si estamos en slope hay que seguirla
// (por si la gravedad nos ha movido o algo)
return;
}
auto [tc, ox, oy] = getCollisionContext();
const auto& tc = room_->getTileCollider();
float new_x = x_ + (vx_ * delta_time);
// Colisión con paredes (room actual)
if (vx_ < 0.0F) {
float wall = tc.checkWallLeft(new_x + ox, y_ + oy, WIDTH, HEIGHT);
if (wall != Collision::NONE) {
new_x = wall - ox;
}
} else {
float wall = tc.checkWallRight(new_x + ox, y_ + oy, WIDTH, HEIGHT);
if (wall != Collision::NONE) {
new_x = wall - WIDTH - ox;
}
// 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;
}
wall = tc.checkWallRight(new_x, y_, WIDTH, HEIGHT);
if (wall != Collision::NONE) {
float corrected = wall - WIDTH;
if (corrected < new_x) { new_x = corrected; }
}
// Cross-room: comprobar muros en rooms adyacentes
auto cross = getCrossRoomChecks();
checkCrossRoomWallH(new_x, cross);
x_ = new_x;
// Si estamos en una slope, ajustar Y para seguirla
if (state_ == State::ON_SLOPE) {
followSlope();
}
// Si estamos en suelo plano, detectar entrada a slope
if (state_ == State::ON_GROUND) {
detectSlopeEntry();
}
// Slope following y detección solo cuando hay movimiento horizontal
if (vx_ == 0.0F) { return; }
if (state_ == State::ON_SLOPE) { followSlope(); }
if (state_ == State::ON_GROUND) { detectSlopeEntry(); }
}
// Ajusta Y del jugador para seguir la superficie de la slope mientras camina.
@@ -274,18 +258,18 @@ void Player::moveHorizontal(float delta_time) {
// actual y la inferior (las slopes en escalera bajan una fila por tile).
// Si no encuentra slope, llama a exitSlope().
void Player::followSlope() {
auto [tc, ox, oy] = getCollisionContext();
const auto& tc = room_->getTileCollider();
// SLOPE_L (\): pie izquierdo. SLOPE_R (/): pie derecho.
float foot_x = (slope_type_ == TileCollider::Tile::SLOPE_L) ? (x_ + ox) : (x_ + ox) + WIDTH - 1;
float foot_x = (slope_type_ == TileCollider::Tile::SLOPE_L) ? x_ : x_ + WIDTH - 1;
// Calcular Y en la slope actual
float surface_y = tc.getSlopeY(slope_tile_x_, slope_tile_y_, foot_x);
y_ = surface_y - HEIGHT - oy;
y_ = surface_y - HEIGHT;
// Comprobar si hemos salido del tile actual
int foot_tile_x = static_cast<int>(foot_x) / Tile::SIZE;
int foot_tile_y = static_cast<int>((y_ + oy) + HEIGHT) / Tile::SIZE;
int foot_tile_y = static_cast<int>(y_ + HEIGHT) / Tile::SIZE;
if (foot_tile_x != slope_tile_x_ || foot_tile_y != slope_tile_y_) {
// Buscar slope en el tile calculado y en el de abajo (la escalera de slopes
@@ -297,7 +281,7 @@ void Player::followSlope() {
slope_tile_y_ = row;
slope_type_ = new_tile;
surface_y = tc.getSlopeY(slope_tile_x_, slope_tile_y_, foot_x);
y_ = surface_y - HEIGHT - oy;
y_ = surface_y - HEIGHT;
return;
}
}
@@ -310,16 +294,16 @@ void Player::followSlope() {
// entre filas cuando se sale por el extremo inferior de la slope).
// Si hay suelo, snapea al borde del tile. Si no, empieza a caer.
void Player::exitSlope() {
auto [tc, ox, oy] = getCollisionContext();
float foot_y = (y_ + oy) + HEIGHT;
const auto& tc = room_->getTileCollider();
float foot_y = y_ + HEIGHT;
// Comprobar suelo en la fila actual y la siguiente (al salir por abajo de una slope,
// los pies pueden estar en el último pixel de la fila, justo antes del suelo)
for (int check = 0; check <= 1; ++check) {
float check_y = foot_y + check;
if (tc.hasGroundBelow(x_ + ox, check_y, WIDTH)) {
if (tc.hasGroundBelow(x_, check_y, WIDTH)) {
int row = static_cast<int>(check_y) / Tile::SIZE;
y_ = static_cast<float>(row * Tile::SIZE) - HEIGHT - oy;
y_ = static_cast<float>(row * Tile::SIZE) - HEIGHT;
transitionToState(State::ON_GROUND);
return;
}
@@ -333,12 +317,12 @@ void Player::exitSlope() {
// Las slopes en escalera están una fila arriba del suelo, así que checkSlopeBelow
// también mira la fila superior.
void Player::detectSlopeEntry() {
auto [tc, ox, oy] = getCollisionContext();
float foot_y = (y_ + oy) + HEIGHT;
const auto& tc = room_->getTileCollider();
float foot_y = y_ + HEIGHT;
auto slope = tc.checkSlopeBelow(x_ + ox, foot_y, WIDTH);
auto slope = tc.checkSlopeBelow(x_, foot_y, WIDTH);
if (slope.on_slope) {
y_ = slope.surface_y - HEIGHT - oy;
y_ = slope.surface_y - HEIGHT;
slope_tile_x_ = slope.tile_x;
slope_tile_y_ = slope.tile_y;
slope_type_ = slope.type;
@@ -353,29 +337,27 @@ void Player::detectSlopeEntry() {
void Player::moveVertical(float delta_time) {
if (state_ != State::ON_AIR) { return; }
auto [tc, ox, oy] = getCollisionContext();
const auto& tc = room_->getTileCollider();
float displacement = vy_ * delta_time;
float old_y = y_;
if (vy_ < 0.0F) {
// Subiendo: comprobar techo
float new_y = y_ + displacement;
float ceiling = tc.checkCeiling(x_ + ox, new_y + oy, WIDTH);
float ceiling = tc.checkCeiling(x_, new_y, WIDTH);
if (ceiling != Collision::NONE) {
y_ = ceiling - oy;
y_ = ceiling;
vy_ = 0.0F;
} else {
y_ = new_y;
}
} else if (vy_ > 0.0F) {
// Bajando: comprobar suelo
float foot_y = (y_ + oy) + HEIGHT;
float foot_y = y_ + HEIGHT;
float new_foot_y = foot_y + displacement;
auto hit = tc.checkFloor(x_ + ox, foot_y, WIDTH, new_foot_y);
auto hit = tc.checkFloor(x_, foot_y, WIDTH, new_foot_y);
if (hit.y != Collision::NONE) {
y_ = hit.y - HEIGHT - oy;
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;
@@ -391,10 +373,6 @@ void Player::moveVertical(float delta_time) {
#endif
}
}
// Cross-room: comprobar suelo/techo en rooms adyacentes
auto cross = getCrossRoomChecks();
checkCrossRoomFloor(old_y, cross);
}
// ============================================================================
@@ -404,7 +382,7 @@ void Player::moveVertical(float delta_time) {
void Player::checkFalling() {
if (state_ == State::ON_AIR) { return; }
auto [tc, ox, oy] = getCollisionContext();
const auto& tc = room_->getTileCollider();
if (state_ == State::ON_SLOPE) {
// Verificar que el tile de slope sigue existiendo
@@ -419,14 +397,15 @@ void Player::checkFalling() {
// ON_GROUND: si está sobre una plataforma móvil, no comprobar tiles
if (on_platform_) { return; }
// ON_GROUND: comprobar si sigue habiendo suelo
float foot_y = (y_ + oy) + HEIGHT;
if (!tc.hasGroundBelow(x_ + ox, foot_y, WIDTH)) {
// ON_GROUND: comprobar si sigue habiendo suelo (el tilemap extendido
// incluye tiles de las rooms adyacentes, así que no hace falta cross-room)
float foot_y = y_ + HEIGHT;
if (!tc.hasGroundBelow(x_, foot_y, WIDTH)) {
// 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_ + ox, foot_y, WIDTH);
auto slope = tc.checkSlopeBelow(x_, foot_y, WIDTH);
if (slope.on_slope) {
y_ = slope.surface_y - HEIGHT - oy;
y_ = slope.surface_y - HEIGHT;
slope_tile_x_ = slope.tile_x;
slope_tile_y_ = slope.tile_y;
slope_type_ = slope.type;
@@ -434,11 +413,6 @@ void Player::checkFalling() {
return;
}
// Cross-room: comprobar suelo en rooms adyacentes antes de declarar caída
if (hasCrossRoomGround(getCrossRoomChecks())) {
return;
}
vy_ = 0.0F;
transitionToState(State::ON_AIR);
}
@@ -491,52 +465,6 @@ auto Player::handleBorders() const -> Room::Border {
return Room::Border::NONE;
}
void Player::setAdjacentRoom(std::shared_ptr<Room> room, Room::Border direction) {
adjacent_room_ = std::move(room);
adjacent_direction_ = direction;
}
void Player::clearAdjacentRoom() {
adjacent_room_.reset();
adjacent_direction_ = Room::Border::NONE;
}
auto Player::getCollisionContext() const -> CollisionContext {
if (!adjacent_room_) {
return {room_->getTileCollider(), 0.0F, 0.0F};
}
const float CENTER_X = x_ + (WIDTH / 2.0F);
const float CENTER_Y = y_ + (HEIGHT / 2.0F);
switch (adjacent_direction_) {
case Room::Border::TOP:
if (CENTER_Y < PlayArea::TOP) {
return {adjacent_room_->getTileCollider(), 0.0F, static_cast<float>(PlayArea::HEIGHT)};
}
break;
case Room::Border::BOTTOM:
if (CENTER_Y > PlayArea::BOTTOM) {
return {adjacent_room_->getTileCollider(), 0.0F, -static_cast<float>(PlayArea::HEIGHT)};
}
break;
case Room::Border::LEFT:
if (CENTER_X < PlayArea::LEFT) {
return {adjacent_room_->getTileCollider(), static_cast<float>(PlayArea::WIDTH), 0.0F};
}
break;
case Room::Border::RIGHT:
if (CENTER_X > PlayArea::RIGHT) {
return {adjacent_room_->getTileCollider(), -static_cast<float>(PlayArea::WIDTH), 0.0F};
}
break;
default:
break;
}
return {room_->getTileCollider(), 0.0F, 0.0F};
}
void Player::switchBorders() {
switch (border_) {
case Room::Border::TOP:
@@ -569,109 +497,6 @@ void Player::syncSpriteAndCollider() {
collider_box_ = getRect();
}
// Cross-room collision: asigna una room adyacente por índice
void Player::setBorderRoom(int index, std::shared_ptr<Room> room) {
if (index >= 0 && index < BORDER_ROOM_COUNT) {
border_rooms_[index] = std::move(room);
}
}
// Cross-room collision: limpia todas las rooms adyacentes
void Player::clearBorderRooms() {
for (auto& r : border_rooms_) {
r.reset();
}
}
// Cross-room: construye la lista de rooms adyacentes que solapan con la bbox del jugador
auto Player::getCrossRoomChecks() const -> CrossRoomChecks {
// Offsets por room: TOP, RIGHT, BOTTOM, LEFT, TR, BR, BL, TL
static constexpr float PW = static_cast<float>(PlayArea::WIDTH);
static constexpr float PH = static_cast<float>(PlayArea::HEIGHT);
static constexpr struct { float ox; float oy; } OFFSETS[BORDER_ROOM_COUNT] = {
{0, PH}, {-PW, 0}, {0, -PH}, {PW, 0}, {-PW, PH}, {-PW, -PH}, {PW, -PH}, {PW, PH}
};
bool over_top = y_ < 0.0F;
bool over_right = (x_ + WIDTH) > PlayArea::RIGHT;
bool over_bottom = (y_ + HEIGHT) > PlayArea::BOTTOM;
bool over_left = x_ < 0.0F;
bool needed[BORDER_ROOM_COUNT] = {
over_top, over_right, over_bottom, over_left,
over_top && over_right, over_bottom && over_right,
over_bottom && over_left, over_top && over_left
};
CrossRoomChecks result;
for (int i = 0; i < BORDER_ROOM_COUNT; ++i) {
if (needed[i] && border_rooms_[i]) {
result.entries[result.count++] = {&border_rooms_[i]->getTileCollider(), OFFSETS[i].ox, OFFSETS[i].oy};
}
}
return result;
}
// Cross-room: comprueba muros horizontales en rooms adyacentes
void Player::checkCrossRoomWallH(float& new_x, const CrossRoomChecks& checks) const {
for (int i = 0; i < checks.count; ++i) {
const auto& [tc, ox, oy] = checks.entries[i];
if (vx_ < 0.0F) {
float wall = tc->checkWallLeft(new_x + ox, y_ + oy, WIDTH, HEIGHT);
if (wall != Collision::NONE) {
float corrected = wall - ox;
if (corrected > new_x) { new_x = corrected; }
}
} else if (vx_ > 0.0F) {
float wall = tc->checkWallRight(new_x + ox, y_ + oy, WIDTH, HEIGHT);
if (wall != Collision::NONE) {
float corrected = wall - WIDTH - ox;
if (corrected < new_x) { new_x = corrected; }
}
}
}
}
// Cross-room: comprueba suelo/techo en rooms adyacentes (usa old_y para rango correcto de checkFloor)
void Player::checkCrossRoomFloor(float old_y, const CrossRoomChecks& checks) {
for (int i = 0; i < checks.count; ++i) {
const auto& [tc, ox, oy] = checks.entries[i];
if (vy_ < 0.0F) {
float ceiling = tc->checkCeiling(x_ + ox, y_ + oy, WIDTH);
if (ceiling != Collision::NONE) {
float corrected = ceiling - oy;
if (corrected > y_) {
y_ = corrected;
vy_ = 0.0F;
}
}
} else if (vy_ > 0.0F) {
float old_foot = old_y + oy + HEIGHT;
float new_foot = y_ + oy + HEIGHT;
auto hit = tc->checkFloor(x_ + ox, old_foot, WIDTH, new_foot);
if (hit.y != Collision::NONE) {
float corrected = hit.y - HEIGHT - oy;
if (corrected < y_) {
y_ = corrected;
vy_ = 0.0F;
transitionToState(State::ON_GROUND);
}
}
}
}
}
// Cross-room: comprueba si hay suelo bajo el jugador en alguna room adyacente
auto Player::hasCrossRoomGround(const CrossRoomChecks& checks) const -> bool {
for (int i = 0; i < checks.count; ++i) {
const auto& [tc, ox, oy] = checks.entries[i];
float foot = y_ + oy + HEIGHT;
if (tc->hasGroundBelow(x_ + ox, foot, WIDTH)) {
return true;
}
}
return false;
}
// 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

View File

@@ -70,18 +70,11 @@ class Player {
auto getSpawnParams() -> SpawnData { return {.x = x_, .y = y_, .vx = vx_, .vy = vy_, .last_grounded_position = last_grounded_position_, .state = state_, .flip = sprite_->getFlip()}; }
static auto skinToAnimationPath(const std::string& skin_name) -> std::string;
void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); }
void setAdjacentRoom(std::shared_ptr<Room> room, Room::Border direction);
void clearAdjacentRoom();
[[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; }
// Cross-room collision: rooms adyacentes (TOP=0, RIGHT=1, BOTTOM=2, LEFT=3, TR=4, BR=5, BL=6, TL=7)
static constexpr int BORDER_ROOM_COUNT = 8;
void setBorderRoom(int index, std::shared_ptr<Room> room);
void clearBorderRooms();
void setPaused(bool value) { is_paused_ = value; }
void setIgnoreInput(bool value) { ignore_input_ = value; }
[[nodiscard]] auto getIgnoreInput() const -> bool { return ignore_input_; }
@@ -96,18 +89,8 @@ class Player {
static constexpr int WIDTH = 12;
static constexpr int HEIGHT = 24;
// --- Contexto de colisión (selección de room + traducción de coordenadas) ---
struct CollisionContext {
const TileCollider& tc;
float offset_x;
float offset_y;
};
auto getCollisionContext() const -> CollisionContext;
// --- Objetos y punteros ---
std::shared_ptr<Room> room_;
std::shared_ptr<Room> adjacent_room_;
Room::Border adjacent_direction_{Room::Border::NONE};
std::unique_ptr<AnimatedSprite> sprite_;
// --- Posición y física ---
@@ -142,9 +125,6 @@ class Player {
Room::Border border_ = Room::Border::TOP;
int last_grounded_position_ = 0;
// --- Cross-room collision ---
std::shared_ptr<Room> border_rooms_[BORDER_ROOM_COUNT]{};
// --- Renderizado y sonido ---
JA_Sound_t* jump_sound_ = nullptr;
JA_Sound_t* land_sound_ = nullptr;
@@ -156,21 +136,6 @@ class Player {
void handleJumpAndDrop();
void moveHorizontal(float delta_time);
void moveVertical(float delta_time);
// Cross-room collision helpers
struct CrossRoomEntry {
const TileCollider* tc;
float ox;
float oy;
};
struct CrossRoomChecks {
CrossRoomEntry entries[BORDER_ROOM_COUNT]{};
int count{0};
};
auto getCrossRoomChecks() const -> CrossRoomChecks;
void checkCrossRoomWallH(float& new_x, const CrossRoomChecks& checks) const;
void checkCrossRoomFloor(float old_y, const CrossRoomChecks& checks);
auto hasCrossRoomGround(const CrossRoomChecks& checks) const -> bool;
void followSlope();
void exitSlope();
void detectSlopeEntry();

View File

@@ -1,8 +1,91 @@
#include "collision_map.hpp"
#include <utility> // Para std::move
#include <algorithm> // Para std::ranges::fill
#include <utility> // Para std::move
CollisionMap::CollisionMap(std::vector<int> collision_tile_map, int conveyor_belt_direction)
: collision_tile_map_(std::move(collision_tile_map)),
extended_tile_map_(EW * EH, 0),
conveyor_belt_direction_(conveyor_belt_direction),
tile_collider_(collision_tile_map_) {}
tile_collider_(extended_tile_map_, EW, EH, CollisionBorder::PX) {
buildExtendedCenter();
}
// Copia los tiles de la room actual al centro del mapa extendido
void CollisionMap::buildExtendedCenter() {
std::ranges::fill(extended_tile_map_, 0);
for (int row = 0; row < MH; ++row) {
for (int col = 0; col < MW; ++col) {
extended_tile_map_[((row + B) * EW) + (col + B)] =
collision_tile_map_[(row * MW) + col];
}
}
}
// Copia una región rectangular de src (MW×MH) al mapa extendido
void CollisionMap::copyRegion(const std::vector<int>& src,
int src_col, int src_row,
int dst_col, int dst_row,
int cols, int rows) {
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < cols; ++c) {
extended_tile_map_[((dst_row + r) * EW) + (dst_col + c)] =
src[((src_row + r) * MW) + (src_col + c)];
}
}
}
// Rellena los bordes del mapa extendido con tiles de las habitaciones adyacentes
void CollisionMap::updateBorders(const AdjacentData& adj) {
// Reconstruir: limpiar bordes y copiar centro
buildExtendedCenter();
// Cardinales
// TOP: últimas B filas de la room de arriba → filas 0..B-1 del extendido
if (adj.top != nullptr) {
copyRegion(*adj.top, 0, MH - B, B, 0, MW, B);
}
// BOTTOM: primeras B filas de la room de abajo → filas B+MH..B+MH+B-1
if (adj.bottom != nullptr) {
copyRegion(*adj.bottom, 0, 0, B, B + MH, MW, B);
}
// LEFT: últimas B columnas de la room izquierda → cols 0..B-1
if (adj.left != nullptr) {
copyRegion(*adj.left, MW - B, 0, 0, B, B, MH);
}
// RIGHT: primeras B columnas de la room derecha → cols B+MW..B+MW+B-1
if (adj.right != nullptr) {
copyRegion(*adj.right, 0, 0, B + MW, B, B, MH);
}
// Diagonales (esquinas B×B)
// TOP-LEFT: esquina inferior-derecha de la room diagonal
if (adj.top_left != nullptr) {
copyRegion(*adj.top_left, MW - B, MH - B, 0, 0, B, B);
}
// TOP-RIGHT: esquina inferior-izquierda de la room diagonal
if (adj.top_right != nullptr) {
copyRegion(*adj.top_right, 0, MH - B, B + MW, 0, B, B);
}
// BOTTOM-LEFT: esquina superior-derecha de la room diagonal
if (adj.bottom_left != nullptr) {
copyRegion(*adj.bottom_left, MW - B, 0, 0, B + MH, B, B);
}
// BOTTOM-RIGHT: esquina superior-izquierda de la room diagonal
if (adj.bottom_right != nullptr) {
copyRegion(*adj.bottom_right, 0, 0, B + MW, B + MH, B, B);
}
}
#ifdef _DEBUG
void CollisionMap::setCollisionTile(int index, int value) {
if (index >= 0 && index < static_cast<int>(collision_tile_map_.size())) {
collision_tile_map_[index] = value;
// Actualizar también el tile correspondiente en el mapa extendido
int row = index / MW;
int col = index % MW;
extended_tile_map_[((row + B) * EW) + (col + B)] = value;
}
}
#endif

View File

@@ -3,15 +3,31 @@
#include <vector>
#include "game/gameplay/tile_collider.hpp"
#include "utils/defines.hpp"
/**
* @brief Mapa de colisiones de una habitación
*
* Contiene el collision_tile_map (grid de tipos de tile) y el TileCollider
* que proporciona queries de colisión directas contra el grid.
* Contiene el collision_tile_map original (32×21) y un tilemap extendido (38×27)
* que incluye tiles de borde de las habitaciones adyacentes.
* El TileCollider opera sobre el mapa extendido, permitiendo colisiones cross-room
* sin lógica especial en el Player.
*/
class CollisionMap {
public:
// Datos de colisión de las habitaciones adyacentes (punteros a sus collision_tile_map).
// nullptr = sin habitación en esa dirección (borde queda como EMPTY).
struct AdjacentData {
const std::vector<int>* top{nullptr};
const std::vector<int>* bottom{nullptr};
const std::vector<int>* left{nullptr};
const std::vector<int>* right{nullptr};
const std::vector<int>* top_left{nullptr};
const std::vector<int>* top_right{nullptr};
const std::vector<int>* bottom_left{nullptr};
const std::vector<int>* bottom_right{nullptr};
};
CollisionMap(std::vector<int> collision_tile_map, int conveyor_belt_direction);
~CollisionMap() = default;
@@ -20,21 +36,36 @@ class CollisionMap {
CollisionMap(CollisionMap&&) = delete;
auto operator=(CollisionMap&&) -> CollisionMap& = delete;
// Rellena los bordes del mapa extendido con tiles de las habitaciones adyacentes.
void updateBorders(const AdjacentData& adjacent);
[[nodiscard]] auto getTileCollider() const -> const TileCollider& { return tile_collider_; }
[[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; }
[[nodiscard]] auto getCollisionTileMap() const -> const std::vector<int>& { return collision_tile_map_; }
#ifdef _DEBUG
void setCollisionTile(int index, int value) {
if (index >= 0 && index < static_cast<int>(collision_tile_map_.size())) {
collision_tile_map_[index] = value;
}
}
void setCollisionTile(int index, int value);
void setConveyorBeltDirection(int direction) { conveyor_belt_direction_ = direction; }
#endif
private:
std::vector<int> collision_tile_map_;
static constexpr int B = CollisionBorder::TILES; // Tiles de borde
static constexpr int MW = Map::WIDTH; // Ancho original (32)
static constexpr int MH = Map::HEIGHT; // Alto original (21)
static constexpr int EW = ExtendedMap::WIDTH; // Ancho extendido (38)
static constexpr int EH = ExtendedMap::HEIGHT; // Alto extendido (27)
std::vector<int> collision_tile_map_; // Original (32×21)
std::vector<int> extended_tile_map_; // Extendido (38×27) — referenciado por TileCollider
int conveyor_belt_direction_;
TileCollider tile_collider_;
TileCollider tile_collider_; // Debe ir después de extended_tile_map_ (usa referencia)
// Copia el centro (room actual) al mapa extendido. Los bordes quedan como EMPTY (0).
void buildExtendedCenter();
// Copia una región rectangular de un tilemap fuente (MW×MH) al mapa extendido.
void copyRegion(const std::vector<int>& src,
int src_col, int src_row,
int dst_col, int dst_row,
int cols, int rows);
};

View File

@@ -215,6 +215,14 @@ auto Room::getTileCollider() const -> const TileCollider& {
return collision_map_->getTileCollider();
}
auto Room::getCollisionTileMap() const -> const std::vector<int>& {
return collision_map_->getCollisionTileMap();
}
void Room::updateCollisionBorders(const CollisionMap::AdjacentData& adjacent) {
collision_map_->updateBorders(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) {

View File

@@ -6,17 +6,17 @@
#include <string> // Para string
#include <vector> // Para vector
#include "game/defaults.hpp" // Para Defaults::Game::Room
#include "game/entities/enemy.hpp" // Para EnemyData
#include "game/entities/item.hpp" // Para ItemData
#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data
#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/enemy.hpp" // Para EnemyData
#include "game/entities/item.hpp" // Para ItemData
#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
class Surface;
class EnemyManager;
class ItemManager;
class PlatformManager;
class CollisionMap;
class TileCollider;
class TilemapRenderer;
@@ -84,6 +84,8 @@ class Room {
void setPaused(bool value);
[[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; }
[[nodiscard]] auto getTileCollider() const -> const TileCollider&;
[[nodiscard]] auto getCollisionTileMap() const -> const std::vector<int>&;
void updateCollisionBorders(const CollisionMap::AdjacentData& adjacent);
// Método de carga de archivos YAML (delegado a RoomLoader)
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Data;

View File

@@ -1,20 +1,19 @@
#include "game/gameplay/tile_collider.hpp"
#include <algorithm> // Para std::min, std::max
#include <cmath> // Para std::ceil
#include <algorithm> // Para std::min, std::max, std::clamp
#include "utils/defines.hpp"
TileCollider::TileCollider(const std::vector<int>& collision_tile_map)
: tile_map_(collision_tile_map) {}
TileCollider::TileCollider(const std::vector<int>& extended_tile_map, int width, int height, int border_px)
: width_(width), height_(height), border_px_(border_px), tile_map_(extended_tile_map) {}
// --- Queries básicas ---
auto TileCollider::getTileAt(int tile_x, int tile_y) const -> Tile {
if (tile_x < 0 || tile_x >= MW || tile_y < 0 || tile_y >= MH) {
if (tile_x < 0 || tile_x >= width_ || tile_y < 0 || tile_y >= height_) {
return Tile::EMPTY;
}
int value = tile_map_[(tile_y * MW) + tile_x];
int value = tile_map_[(tile_y * width_) + tile_x];
if (value >= 0 && value <= 5) {
return static_cast<Tile>(value);
}
@@ -30,8 +29,8 @@ auto TileCollider::isSolid(int tile_x, int tile_y) const -> bool {
// SLOPE_L (\): alto a la izquierda, bajo a la derecha. surface = bottom - (7 - x_in_tile)
// SLOPE_R (/): alto a la derecha, bajo a la izquierda. surface = bottom - x_in_tile
auto TileCollider::getSlopeY(int tile_x, int tile_y, float px) const -> float {
auto tile_bottom = static_cast<float>(((tile_y + 1) * TS) - 1);
float x_in_tile = px - static_cast<float>(tile_x * TS);
float tile_bottom = toPixel(tile_y + 1) - 1;
float x_in_tile = px - toPixel(tile_x);
x_in_tile = std::clamp(x_in_tile, 0.0F, static_cast<float>(TS - 1));
auto tile = getTileAt(tile_x, tile_y);
@@ -54,7 +53,7 @@ auto TileCollider::checkWallLeft(float x, float y, float w, float h) const -> fl
for (int row = top_row; row <= bot_row; ++row) {
if (isSolid(col, row)) {
return static_cast<float>((col + 1) * TS);
return toPixel(col + 1);
}
}
return Collision::NONE;
@@ -67,7 +66,7 @@ auto TileCollider::checkWallRight(float x, float y, float w, float h) const -> f
for (int row = top_row; row <= bot_row; ++row) {
if (isSolid(col, row)) {
return static_cast<float>(col * TS);
return toPixel(col);
}
}
return Collision::NONE;
@@ -84,7 +83,7 @@ auto TileCollider::checkCeiling(float x, float y, float w) const -> float {
auto tile = getTileAt(col, top_row);
// Slopes actúan como techo (no se atraviesan desde abajo)
if (tile == Tile::WALL || tile == Tile::SLOPE_L || tile == Tile::SLOPE_R) {
return static_cast<float>((top_row + 1) * TS);
return toPixel(top_row + 1);
}
}
return Collision::NONE;
@@ -93,7 +92,7 @@ auto TileCollider::checkCeiling(float x, float y, float w) const -> float {
// --- Colisión con suelo (landing) ---
// Busca suelo entre foot_y_current y foot_y_new (rango de caída del frame).
// WALL: siempre bloquea.
// WALL: bloquea si los pies estaban por encima (como PASSABLE).
// PASSABLE: solo si los pies estaban por encima del borde superior del tile.
// SLOPE: siempre bloquea (las slopes son sólidas, como muros en diagonal).
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
@@ -111,9 +110,12 @@ auto TileCollider::checkFloor(float x, float foot_y_current, float w, float foot
float floor_y = Collision::NONE;
if (tile == Tile::WALL) {
floor_y = static_cast<float>(row * TS);
float tile_top = toPixel(row);
if (foot_y_current <= tile_top) {
floor_y = tile_top;
}
} else if (tile == Tile::PASSABLE) {
auto tile_top = static_cast<float>(row * TS);
float tile_top = toPixel(row);
// Solo cuenta como suelo si los pies estaban por encima antes del movimiento
if (foot_y_current <= tile_top) {
floor_y = tile_top;

View File

@@ -30,7 +30,9 @@ class TileCollider {
float surface_y{0};
};
explicit TileCollider(const std::vector<int>& collision_tile_map);
// Constructor: recibe el tilemap extendido y sus dimensiones.
// border_px es el offset en píxeles para traducir coordenadas de room-space a extended-map.
TileCollider(const std::vector<int>& extended_tile_map, int width, int height, int border_px);
// Queries básicas
[[nodiscard]] auto getTileAt(int tile_x, int tile_y) const -> Tile;
@@ -53,10 +55,19 @@ class TileCollider {
private:
static constexpr int TS = ::Tile::SIZE;
static constexpr int MW = ::Map::WIDTH;
static constexpr int MH = ::Map::HEIGHT;
int width_; // Ancho total del tilemap extendido (en tiles)
int height_; // Alto total del tilemap extendido (en tiles)
int border_px_; // Offset en píxeles (CollisionBorder::PX)
const std::vector<int>& tile_map_;
[[nodiscard]] static auto toTile(int px) -> int { return px / TS; }
// Convierte píxeles en room-space a índice de tile en el mapa extendido.
// Nota: asume que px >= -border_px_ (el jugador no puede estar más allá del borde).
[[nodiscard]] auto toTile(int px) const -> int { return (px + border_px_) / TS; }
// Convierte índice de tile del mapa extendido a píxeles en room-space.
[[nodiscard]] auto toPixel(int tile) const -> float {
return static_cast<float>((tile * TS) - border_px_);
}
};

View File

@@ -71,6 +71,7 @@ Game::Game(Mode mode)
initPlayer(spawn_data_, room_);
game_backbuffer_surface_ = std::make_shared<Surface>(Options::game.width, Options::game.height);
changeRoom(current_room_);
buildCollisionBorders();
if (Console::get() != nullptr) {
#ifdef _DEBUG
@@ -131,6 +132,7 @@ Game::Game(Mode mode)
// Recargar la habitación desde disco (con los cambios del editor)
Resource::Cache::get()->reloadRoom(current_room_);
changeRoom(current_room_);
buildCollisionBorders();
player_->setRoom(room_);
};
GameControl::revert_editor = []() -> std::string {
@@ -310,39 +312,6 @@ void Game::updatePlaying(float delta_time) {
player_->clearPlatformFlag();
checkPlayerAndPlatforms();
// Cross-room collision: precargar rooms adyacentes para colisiones
{
player_->clearBorderRooms();
auto loadBorder = [&](Room::Border b) -> std::shared_ptr<Room> {
auto name = room_->getRoom(b);
return (name != "0") ? getOrCreateRoom(name) : nullptr;
};
auto top = loadBorder(Room::Border::TOP);
auto right = loadBorder(Room::Border::RIGHT);
auto bottom = loadBorder(Room::Border::BOTTOM);
auto left = loadBorder(Room::Border::LEFT);
if (top) { player_->setBorderRoom(0, top); }
if (right) { player_->setBorderRoom(1, right); }
if (bottom) { player_->setBorderRoom(2, bottom); }
if (left) { player_->setBorderRoom(3, left); }
// Diagonales
auto loadDiag = [&](const std::shared_ptr<Room>& a, Room::Border ab,
const std::shared_ptr<Room>& b, Room::Border ba) -> std::shared_ptr<Room> {
std::string name;
if (a) { name = a->getRoom(ab); }
if ((name.empty() || name == "0") && b) { name = b->getRoom(ba); }
return (!name.empty() && name != "0") ? getOrCreateRoom(name) : nullptr;
};
auto tr = loadDiag(top, Room::Border::RIGHT, right, Room::Border::TOP);
auto br = loadDiag(bottom, Room::Border::RIGHT, right, Room::Border::BOTTOM);
auto bl = loadDiag(bottom, Room::Border::LEFT, left, Room::Border::BOTTOM);
auto tl = loadDiag(top, Room::Border::LEFT, left, Room::Border::TOP);
if (tr) { player_->setBorderRoom(4, tr); }
if (br) { player_->setBorderRoom(5, br); }
if (bl) { player_->setBorderRoom(6, bl); }
if (tl) { player_->setBorderRoom(7, tl); }
}
#ifdef _DEBUG
// Maneja el arrastre del jugador con el ratón (debug)
handleDebugMouseDrag(delta_time);
@@ -452,6 +421,7 @@ void Game::transitionToState(State new_state) {
// Respawn room y player
room_ = getOrCreateRoom(current_room_);
initPlayer(spawn_data_, room_);
buildCollisionBorders();
// Pausar ambos
room_->setPaused(true);
player_->setPaused(true);
@@ -825,6 +795,47 @@ auto Game::getOrCreateRoom(const std::string& room_path) -> std::shared_ptr<Room
return room;
}
// Construye el tilemap extendido de la room actual con los bordes de las adyacentes
void Game::buildCollisionBorders() {
// Helper: obtiene el collision tilemap de una room adyacente (nullptr si no existe)
auto getAdjacentCollision = [&](Room::Border b) -> const std::vector<int>* {
auto name = room_->getRoom(b);
if (name == "0") { return nullptr; }
return &getOrCreateRoom(name)->getCollisionTileMap();
};
// Helper: obtiene el collision tilemap de una room diagonal (A→B o C→D)
auto getDiagCollision = [&](Room::Border first, Room::Border second) -> const std::vector<int>* {
// Camino 1: room en dirección 'first', luego su adyacente en dirección 'second'
auto name1 = room_->getRoom(first);
if (name1 != "0") {
auto r = getOrCreateRoom(name1);
auto name2 = r->getRoom(second);
if (name2 != "0") { return &getOrCreateRoom(name2)->getCollisionTileMap(); }
}
// Camino 2: room en dirección 'second', luego su adyacente en dirección 'first'
auto name3 = room_->getRoom(second);
if (name3 != "0") {
auto r = getOrCreateRoom(name3);
auto name4 = r->getRoom(first);
if (name4 != "0") { return &getOrCreateRoom(name4)->getCollisionTileMap(); }
}
return nullptr;
};
CollisionMap::AdjacentData adj;
adj.top = getAdjacentCollision(Room::Border::TOP);
adj.bottom = getAdjacentCollision(Room::Border::BOTTOM);
adj.left = getAdjacentCollision(Room::Border::LEFT);
adj.right = getAdjacentCollision(Room::Border::RIGHT);
adj.top_left = getDiagCollision(Room::Border::TOP, Room::Border::LEFT);
adj.top_right = getDiagCollision(Room::Border::TOP, Room::Border::RIGHT);
adj.bottom_left = getDiagCollision(Room::Border::BOTTOM, Room::Border::LEFT);
adj.bottom_right = getDiagCollision(Room::Border::BOTTOM, Room::Border::RIGHT);
room_->updateCollisionBorders(adj);
}
// Actualiza los enemigos de las habitaciones adyacentes a la actual
void Game::updateAdjacentRooms(float delta_time) {
for (auto border : {Room::Border::TOP, Room::Border::RIGHT, Room::Border::BOTTOM, Room::Border::LEFT}) {
@@ -869,26 +880,8 @@ void Game::checkPlayerIsOnBorder() {
// Crear nueva habitación y reposicionar jugador
if (changeRoom(ROOM_NAME)) {
// Pasar la room saliente como adyacente al player (para colisiones offscreen)
// La dirección es la opuesta: si salimos por TOP, la vieja queda en BOTTOM
Room::Border opposite = Room::Border::NONE;
switch (BORDER) {
case Room::Border::TOP:
opposite = Room::Border::BOTTOM;
break;
case Room::Border::BOTTOM:
opposite = Room::Border::TOP;
break;
case Room::Border::LEFT:
opposite = Room::Border::RIGHT;
break;
case Room::Border::RIGHT:
opposite = Room::Border::LEFT;
break;
default:
break;
}
player_->setAdjacentRoom(transition_old_room_, opposite);
// Construir el tilemap extendido de la nueva room con los bordes adyacentes
buildCollisionBorders();
player_->switchBorders();
spawn_data_ = player_->getSpawnParams();
@@ -908,10 +901,6 @@ void Game::checkPlayerIsOnBorder() {
killPlayer();
}
}
} else {
// Jugador dentro de los límites: liberar la room adyacente que se mantuvo
// tras endTransition() para evitar frames sin datos de colisión offscreen.
player_->clearAdjacentRoom();
}
}
@@ -925,9 +914,6 @@ void Game::endTransition() {
transition_timer_ = 0.0F;
transition_old_room_.reset();
transition_direction_ = Room::Border::NONE;
// No limpiar adjacent room aquí: el jugador puede estar más allá del borde
// y necesita los datos de colisión de la room adyacente en los frames siguientes.
// Se limpia en checkPlayerIsOnBorder() cuando el jugador está dentro de los límites.
Screen::get()->setRenderOffset(0, 0);
}

View File

@@ -70,6 +70,7 @@ class Game {
static void renderPostFadeEnding(); // Renderiza el juego en estado POST_FADE_ENDING (pantalla negra)
auto changeRoom(const std::string& room_path) -> bool; // Cambia de habitación
auto getOrCreateRoom(const std::string& room_path) -> std::shared_ptr<Room>; // Obtiene una habitación del caché o la crea
void buildCollisionBorders(); // Rellena el tilemap extendido de la room actual con bordes adyacentes
void updateAdjacentRooms(float delta_time); // Actualiza enemigos de las habitaciones adyacentes
void handleInput(); // Comprueba el teclado
void checkPlayerIsOnBorder(); // Comprueba si el jugador esta en el borde de la pantalla y actua

View File

@@ -277,7 +277,7 @@ void Console::toggle() {
input_line_.clear();
cursor_timer_ = 0.0F;
cursor_visible_ = true;
// El mensaje inicial ("JDD Console v1.0") aparece completo, sin typewriter
// El mensaje inicial (nombre de la consola) aparece completo, sin typewriter
typewriter_chars_ = static_cast<int>(msg_lines_[0].size());
typewriter_timer_ = 0.0F;
SDL_StartTextInput(SDL_GetKeyboardFocus());

View File

@@ -60,7 +60,7 @@ class Console {
static constexpr float SLIDE_SPEED = 180.0F;
// Constantes de consola
static constexpr std::string_view CONSOLE_NAME = "JDD Console";
static constexpr std::string_view CONSOLE_NAME = "Projecte 2026 Console";
static constexpr std::string_view CONSOLE_VERSION = "v2.2";
static constexpr int MAX_LINE_CHARS = 32;
static constexpr int MAX_HISTORY_SIZE = 20;

View File

@@ -1194,7 +1194,7 @@ auto CommandRegistry::execute(const std::string& keyword, const std::vector<std:
auto CommandRegistry::generateTerminalHelp() const -> std::string {
std::ostringstream out;
out << "=== JDD CONSOLE COMMANDS ===" << '\n';
out << "=== Project 2026 CONSOLE COMMANDS ===" << '\n';
std::string current_category;
for (const auto& cmd : commands_) {