From 4f890586f142eaedc6b6a823db2d63e68467589f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 9 Apr 2026 21:46:45 +0200 Subject: [PATCH] =?UTF-8?q?treballant=20en=20el=20extendedMap=20per=20a=20?= =?UTF-8?q?les=20colisi=C3=B3ns=20fora=20de=20pantalla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/assets.yaml | 7 + data/platforms/platform1.gif | Bin 0 -> 266 bytes data/platforms/platform1.yaml | 10 + data/room/01.yaml | 23 +- data/room/02.yaml | 22 +- source/core/resources/resource_pack.hpp | 8 +- source/game/entities/player.cpp | 269 +++++------------------- source/game/entities/player.hpp | 35 --- source/game/gameplay/collision_map.cpp | 87 +++++++- source/game/gameplay/collision_map.hpp | 49 ++++- source/game/gameplay/room.cpp | 8 + source/game/gameplay/room.hpp | 16 +- source/game/gameplay/tile_collider.cpp | 30 +-- source/game/gameplay/tile_collider.hpp | 19 +- source/game/scenes/game.cpp | 106 ++++------ source/game/scenes/game.hpp | 1 + source/game/ui/console.cpp | 2 +- source/game/ui/console.hpp | 2 +- source/game/ui/console_commands.cpp | 2 +- source/utils/defines.hpp | 13 ++ 20 files changed, 326 insertions(+), 383 deletions(-) create mode 100644 data/platforms/platform1.gif create mode 100644 data/platforms/platform1.yaml diff --git a/config/assets.yaml b/config/assets.yaml index 2262da1..3f7b796 100644 --- a/config/assets.yaml +++ b/config/assets.yaml @@ -197,6 +197,13 @@ assets: - ${PREFIX}/data/enemies/wave.gif - ${PREFIX}/data/enemies/z80.gif + # PLATFORMS + platforms: + ANIMATION: + - ${PREFIX}/data/platforms/platform1.yaml + BITMAP: + - ${PREFIX}/data/platforms/platform1.gif + # PLAYER player: BITMAP: diff --git a/data/platforms/platform1.gif b/data/platforms/platform1.gif new file mode 100644 index 0000000000000000000000000000000000000000..d8def82ffa3b2d2cdc38b20f405537d295e625e7 GIT binary patch literal 266 zcmV+l0rmbzNk%v~VHf}i0M!5hE+Q{JHAZ4pWtMDgtCV_aPhx*?ikqvl$K25W|Nm|$ zC$l&)>Q6THeM+t)H0V1!`)eKdmKXWLD|#?Ro<%v$Xg%hTPW!o3Og uint32_t; // Validation private: - static constexpr std::array MAGIC_HEADER = {'J', 'D', 'D', 'I'}; // Pack format constants - static constexpr uint32_t VERSION = 1; - static constexpr const char* DEFAULT_ENCRYPT_KEY = "JDDI_RESOURCES_2024"; + static constexpr std::array MAGIC_HEADER = {'P', '2', '6', 'R'}; // Pack format constants + static constexpr uint32_t VERSION = 2; + static constexpr const char* DEFAULT_ENCRYPT_KEY = "P26_RESOURCES_2026"; static auto calculateChecksum(const std::vector& data) -> uint32_t; // Utility methods diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index b6295d6..645ef7f 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -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(foot_y) / Tile::SIZE; - int left_col = static_cast(x_ + ox) / Tile::SIZE; - int right_col = static_cast((x_ + ox) + WIDTH - 1) / Tile::SIZE; + int left_col = static_cast(x_) / Tile::SIZE; + int right_col = static_cast(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(foot_x) / Tile::SIZE; - int foot_tile_y = static_cast((y_ + oy) + HEIGHT) / Tile::SIZE; + int foot_tile_y = static_cast(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(check_y) / Tile::SIZE; - y_ = static_cast(row * Tile::SIZE) - HEIGHT - oy; + y_ = static_cast(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::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(PlayArea::HEIGHT)}; - } - break; - case Room::Border::BOTTOM: - if (CENTER_Y > PlayArea::BOTTOM) { - return {adjacent_room_->getTileCollider(), 0.0F, -static_cast(PlayArea::HEIGHT)}; - } - break; - case Room::Border::LEFT: - if (CENTER_X < PlayArea::LEFT) { - return {adjacent_room_->getTileCollider(), static_cast(PlayArea::WIDTH), 0.0F}; - } - break; - case Room::Border::RIGHT: - if (CENTER_X > PlayArea::RIGHT) { - return {adjacent_room_->getTileCollider(), -static_cast(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) { - 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(PlayArea::WIDTH); - static constexpr float PH = static_cast(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 diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 04f866e..3b4c888 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -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_ = std::move(room); } - void setAdjacentRoom(std::shared_ptr 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); - 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_; - std::shared_ptr adjacent_room_; - Room::Border adjacent_direction_{Room::Border::NONE}; std::unique_ptr 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 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(); diff --git a/source/game/gameplay/collision_map.cpp b/source/game/gameplay/collision_map.cpp index a68ef4a..4d48036 100644 --- a/source/game/gameplay/collision_map.cpp +++ b/source/game/gameplay/collision_map.cpp @@ -1,8 +1,91 @@ #include "collision_map.hpp" -#include // Para std::move +#include // Para std::ranges::fill +#include // Para std::move CollisionMap::CollisionMap(std::vector 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& 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(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 diff --git a/source/game/gameplay/collision_map.hpp b/source/game/gameplay/collision_map.hpp index 09afec6..3777739 100644 --- a/source/game/gameplay/collision_map.hpp +++ b/source/game/gameplay/collision_map.hpp @@ -3,15 +3,31 @@ #include #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* top{nullptr}; + const std::vector* bottom{nullptr}; + const std::vector* left{nullptr}; + const std::vector* right{nullptr}; + const std::vector* top_left{nullptr}; + const std::vector* top_right{nullptr}; + const std::vector* bottom_left{nullptr}; + const std::vector* bottom_right{nullptr}; + }; + CollisionMap(std::vector 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& { return collision_tile_map_; } #ifdef _DEBUG - void setCollisionTile(int index, int value) { - if (index >= 0 && index < static_cast(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 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 collision_tile_map_; // Original (32×21) + std::vector 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& src, + int src_col, int src_row, + int dst_col, int dst_row, + int cols, int rows); }; diff --git a/source/game/gameplay/room.cpp b/source/game/gameplay/room.cpp index 5c535c2..c22894d 100644 --- a/source/game/gameplay/room.cpp +++ b/source/game/gameplay/room.cpp @@ -215,6 +215,14 @@ auto Room::getTileCollider() const -> const TileCollider& { return collision_map_->getTileCollider(); } +auto Room::getCollisionTileMap() const -> const std::vector& { + 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) { diff --git a/source/game/gameplay/room.hpp b/source/game/gameplay/room.hpp index 254006b..ff2cb3e 100644 --- a/source/game/gameplay/room.hpp +++ b/source/game/gameplay/room.hpp @@ -6,17 +6,17 @@ #include // Para string #include // 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&; + 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; diff --git a/source/game/gameplay/tile_collider.cpp b/source/game/gameplay/tile_collider.cpp index 71ecd1f..11bee07 100644 --- a/source/game/gameplay/tile_collider.cpp +++ b/source/game/gameplay/tile_collider.cpp @@ -1,20 +1,19 @@ #include "game/gameplay/tile_collider.hpp" -#include // Para std::min, std::max -#include // Para std::ceil +#include // Para std::min, std::max, std::clamp #include "utils/defines.hpp" -TileCollider::TileCollider(const std::vector& collision_tile_map) - : tile_map_(collision_tile_map) {} +TileCollider::TileCollider(const std::vector& 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(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(((tile_y + 1) * TS) - 1); - float x_in_tile = px - static_cast(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(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((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(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((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(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(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; diff --git a/source/game/gameplay/tile_collider.hpp b/source/game/gameplay/tile_collider.hpp index 81dd693..32069b2 100644 --- a/source/game/gameplay/tile_collider.hpp +++ b/source/game/gameplay/tile_collider.hpp @@ -30,7 +30,9 @@ class TileCollider { float surface_y{0}; }; - explicit TileCollider(const std::vector& 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& 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& 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((tile * TS) - border_px_); + } }; diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index 3f4b3c5..32494a2 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -71,6 +71,7 @@ Game::Game(Mode mode) initPlayer(spawn_data_, room_); game_backbuffer_surface_ = std::make_shared(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 { - 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& a, Room::Border ab, - const std::shared_ptr& b, Room::Border ba) -> std::shared_ptr { - 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 const std::vector* { + 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* { + // 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); } diff --git a/source/game/scenes/game.hpp b/source/game/scenes/game.hpp index 9fff024..435216c 100644 --- a/source/game/scenes/game.hpp +++ b/source/game/scenes/game.hpp @@ -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; // 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 diff --git a/source/game/ui/console.cpp b/source/game/ui/console.cpp index f7caae5..ed0f008 100644 --- a/source/game/ui/console.cpp +++ b/source/game/ui/console.cpp @@ -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(msg_lines_[0].size()); typewriter_timer_ = 0.0F; SDL_StartTextInput(SDL_GetKeyboardFocus()); diff --git a/source/game/ui/console.hpp b/source/game/ui/console.hpp index ddac802..7d65731 100644 --- a/source/game/ui/console.hpp +++ b/source/game/ui/console.hpp @@ -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; diff --git a/source/game/ui/console_commands.cpp b/source/game/ui/console_commands.cpp index 07c34db..73a1de2 100644 --- a/source/game/ui/console_commands.cpp +++ b/source/game/ui/console_commands.cpp @@ -1194,7 +1194,7 @@ auto CommandRegistry::execute(const std::string& keyword, const std::vector 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_) { diff --git a/source/utils/defines.hpp b/source/utils/defines.hpp index c7449bf..d20276c 100644 --- a/source/utils/defines.hpp +++ b/source/utils/defines.hpp @@ -21,6 +21,19 @@ namespace Map { constexpr int HEIGHT = 21; // Alto del mapa en tiles (pantalla menos 3 tiles de statusbar) } // namespace Map +// Borde extra alrededor del tilemap de colisión para cross-room collision. +// Debe ser >= ceil(max(Player::WIDTH, Player::HEIGHT) / Tile::SIZE). +namespace CollisionBorder { + constexpr int TILES = 3; // Tiles de borde por lado + constexpr int PX = TILES * Tile::SIZE; // Píxeles de borde por lado +} // namespace CollisionBorder + +// Tilemap de colisión extendido (room + bordes de las adyacentes) +namespace ExtendedMap { + constexpr int WIDTH = Map::WIDTH + (2 * CollisionBorder::TILES); // 38 + constexpr int HEIGHT = Map::HEIGHT + (2 * CollisionBorder::TILES); // 27 +} // namespace ExtendedMap + namespace PlayArea { constexpr int TOP = (0 * Tile::SIZE); constexpr int BOTTOM = (Map::HEIGHT * Tile::SIZE);