From 53ee49750923deb6b25c2dd58e5b57fb687f0274 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Apr 2026 13:54:50 +0200 Subject: [PATCH] proves3 --- source/game/entities/player.cpp | 65 ++++----- source/game/entities/player.hpp | 3 +- source/game/scenes/game.cpp | 228 +++++++++++++------------------- source/game/scenes/game.hpp | 9 +- 4 files changed, 119 insertions(+), 186 deletions(-) diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index f79b3da..7e00373 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -446,31 +446,6 @@ auto Player::handleBorders() const -> Room::Border { return Room::Border::NONE; } -// Reposiciona al jugador al hacer commit definitivo a la room adyacente. -// Se llama cuando el rectángulo completo del jugador ha salido de bounds. -void Player::commitToRoom(Room::Border border) { - switch (border) { - case Room::Border::TOP: - y_ += PlayArea::HEIGHT; - last_grounded_position_ = static_cast(y_); - break; - case Room::Border::BOTTOM: - y_ -= PlayArea::HEIGHT; - last_grounded_position_ = static_cast(y_); - break; - case Room::Border::RIGHT: - x_ -= PlayArea::WIDTH; - break; - case Room::Border::LEFT: - x_ += PlayArea::WIDTH; - break; - default: - break; - } - border_ = Room::Border::NONE; - syncSpriteAndCollider(); -} - void Player::setAdjacentRoom(std::shared_ptr room, Room::Border direction) { adjacent_room_ = std::move(room); adjacent_direction_ = direction; @@ -481,23 +456,6 @@ void Player::clearAdjacentRoom() { adjacent_direction_ = Room::Border::NONE; } -auto Player::isFullyOutOfBounds() const -> bool { - switch (adjacent_direction_) { - case Room::Border::TOP: - return (y_ + HEIGHT) <= PlayArea::TOP; - case Room::Border::BOTTOM: - return y_ >= PlayArea::BOTTOM; - case Room::Border::LEFT: - return (x_ + WIDTH) <= PlayArea::LEFT; - case Room::Border::RIGHT: - return x_ >= PlayArea::RIGHT; - default: - return false; - } -} - -// Devuelve el TileCollider correcto y los offsets de traducción de coordenadas -// según en qué room está el centro del jugador. auto Player::getCollisionContext() const -> CollisionContext { if (!adjacent_room_) { return {room_->getTileCollider(), 0.0F, 0.0F}; @@ -534,6 +492,29 @@ auto Player::getCollisionContext() const -> CollisionContext { return {room_->getTileCollider(), 0.0F, 0.0F}; } +void Player::switchBorders() { + switch (border_) { + case Room::Border::TOP: + y_ += PlayArea::HEIGHT; + last_grounded_position_ = static_cast(y_); + break; + case Room::Border::BOTTOM: + y_ -= PlayArea::HEIGHT; + last_grounded_position_ = static_cast(y_); + break; + case Room::Border::RIGHT: + x_ -= PlayArea::WIDTH; + break; + case Room::Border::LEFT: + x_ += PlayArea::WIDTH; + break; + default: + break; + } + border_ = Room::Border::NONE; + syncSpriteAndCollider(); +} + // ============================================================================ // Geometría y renderizado // ============================================================================ diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index c758499..2e9f8de 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -63,7 +63,7 @@ class Player { void update(float delta_time); [[nodiscard]] auto isOnBorder() const -> bool { return border_ != Room::Border::NONE; } [[nodiscard]] auto getBorder() const -> Room::Border { return border_; } - void commitToRoom(Room::Border border); + void switchBorders(); auto getRect() -> SDL_FRect { return {.x = x_, .y = y_, .w = WIDTH, .h = HEIGHT}; } auto getCollider() -> SDL_FRect& { return collider_box_; } auto getSpawnParams() -> SpawnData { return {.x = x_, .y = y_, .vx = vx_, .vy = vy_, .last_grounded_position = last_grounded_position_, .state = state_, .flip = sprite_->getFlip()}; } @@ -73,7 +73,6 @@ class Player { void setRoom(std::shared_ptr room) { room_ = std::move(room); } void setAdjacentRoom(std::shared_ptr room, Room::Border direction); void clearAdjacentRoom(); - [[nodiscard]] auto isFullyOutOfBounds() const -> bool; [[nodiscard]] auto isAlive() const -> bool { return is_alive_; } void setPaused(bool value) { is_paused_ = value; } void setIgnoreInput(bool value) { ignore_input_ = value; } diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index 76e15d4..2a900db 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -28,6 +28,7 @@ #include "game/ui/console.hpp" // Para Console #include "game/ui/notifier.hpp" // Para Notifier, NotificationText, CHEEVO_NO... #include "utils/defines.hpp" // Para Tile::SIZE, PlayArea::HEIGHT, RoomBorder::BOTTOM +#include "utils/easing_functions.hpp" // Para Easing::cubicInOut #include "utils/utils.hpp" #ifdef _DEBUG @@ -308,8 +309,8 @@ void Game::updatePlaying(float delta_time) { // Actualiza los objetos room_->update(delta_time); - if (transitioning_ && transition_adjacent_room_) { - transition_adjacent_room_->update(delta_time); + if (transitioning_) { + transition_old_room_->update(delta_time); } switch (mode_) { case Mode::GAME: @@ -329,9 +330,12 @@ void Game::updatePlaying(float delta_time) { checkPlayerAndEnemies(); checkIfPlayerIsAlive(); - // Actualizar cámara de transición + // Avanzar transición if (transitioning_) { - updateTransitionCamera(delta_time); + transition_timer_ += delta_time; + if (transition_timer_ >= TRANSITION_DURATION) { + endTransition(); + } } break; @@ -467,11 +471,44 @@ void Game::renderPlaying() { if (transitioning_) { // --- Transición animada entre pantallas --- - int cam_x = static_cast(camera_offset_x_); - int cam_y = static_cast(camera_offset_y_); + const float T = std::min(transition_timer_ / TRANSITION_DURATION, 1.0F); + const float P = Easing::cubicInOut(T); - // Renderizar habitación principal con offset de cámara - Screen::get()->setRenderOffset(cam_x, cam_y); + // Calcular offsets (derivar uno del otro para evitar gap de 1px por truncamiento) + int old_ox = 0; + int old_oy = 0; + int new_ox = 0; + int new_oy = 0; + + switch (transition_direction_) { + case Room::Border::RIGHT: + new_ox = static_cast((1.0F - P) * PlayArea::WIDTH); + old_ox = new_ox - PlayArea::WIDTH; + break; + case Room::Border::LEFT: + new_ox = -static_cast((1.0F - P) * PlayArea::WIDTH); + old_ox = new_ox + PlayArea::WIDTH; + break; + case Room::Border::BOTTOM: + new_oy = static_cast((1.0F - P) * PlayArea::HEIGHT); + old_oy = new_oy - PlayArea::HEIGHT; + break; + case Room::Border::TOP: + new_oy = -static_cast((1.0F - P) * PlayArea::HEIGHT); + old_oy = new_oy + PlayArea::HEIGHT; + break; + default: + break; + } + + // Renderizar habitación saliente con su offset + Screen::get()->setRenderOffset(old_ox, old_oy); + transition_old_room_->renderMap(); + transition_old_room_->renderEnemies(); + transition_old_room_->renderItems(); + + // Renderizar habitación entrante + jugador con su offset + Screen::get()->setRenderOffset(new_ox, new_oy); room_->renderMap(); room_->renderEnemies(); room_->renderItems(); @@ -479,23 +516,6 @@ void Game::renderPlaying() { player_->render(); } - // Renderizar habitación adyacente: misma cámara pero desplazada una pantalla - if (transition_adjacent_room_) { - int adj_x = cam_x; - int adj_y = cam_y; - switch (transition_direction_) { - case Room::Border::TOP: adj_y += -PlayArea::HEIGHT; break; - case Room::Border::BOTTOM: adj_y += PlayArea::HEIGHT; break; - case Room::Border::LEFT: adj_x += -PlayArea::WIDTH; break; - case Room::Border::RIGHT: adj_x += PlayArea::WIDTH; break; - default: break; - } - Screen::get()->setRenderOffset(adj_x, adj_y); - transition_adjacent_room_->renderMap(); - transition_adjacent_room_->renderEnemies(); - transition_adjacent_room_->renderItems(); - } - // Scoreboard sin offset Screen::get()->setRenderOffset(0, 0); scoreboard_->render(); @@ -773,137 +793,71 @@ auto Game::changeRoom(const std::string& room_path) -> bool { // Comprueba si el jugador esta en el borde de la pantalla void Game::checkPlayerIsOnBorder() { - if (!player_->isOnBorder()) { - // Si hay transición activa y el jugador ha vuelto completamente dentro de bounds, - // comprobar si la cámara también ha vuelto para cancelar la transición - if (transitioning_ && std::abs(camera_offset_x_) < 1.0F && std::abs(camera_offset_y_) < 1.0F) { - endTransition(); - } + // No permitir transiciones encadenadas + if (transitioning_) { + return; + } + if (transition_just_ended_) { + transition_just_ended_ = false; return; } - const auto BORDER = player_->getBorder(); + if (player_->isOnBorder()) { + const auto BORDER = player_->getBorder(); + const auto ROOM_NAME = room_->getRoom(BORDER); - // Si ya hay transición activa, comprobar si el jugador hace commit a la room adyacente - if (transitioning_) { - // ¿El jugador ha salido completamente por el lado de la transición? - if (player_->isFullyOutOfBounds()) { - // Commit: la room adyacente pasa a ser la room principal - room_ = transition_adjacent_room_; - player_->setRoom(room_); - player_->commitToRoom(transition_direction_); - current_room_ = transition_adjacent_room_path_; - spawn_data_ = player_->getSpawnParams(); - setScoreBoardColor(); + // Si no hay habitación adyacente + if (ROOM_NAME == "0") { + if (BORDER == Room::Border::BOTTOM) { + killPlayer(); + } + return; + } - // Ajustar cámara: restar el desplazamiento de una pantalla completa - switch (transition_direction_) { - case Room::Border::TOP: camera_offset_y_ -= PlayArea::HEIGHT; break; - case Room::Border::BOTTOM: camera_offset_y_ += PlayArea::HEIGHT; break; - case Room::Border::LEFT: camera_offset_x_ -= PlayArea::WIDTH; break; - case Room::Border::RIGHT: camera_offset_x_ += PlayArea::WIDTH; break; + // Guardar la habitación saliente + transition_old_room_ = room_; + transition_direction_ = BORDER; + + // 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); - // Limpiar transición (pero la cámara sigue animándose hacia 0) - player_->clearAdjacentRoom(); - transition_adjacent_room_.reset(); - transition_adjacent_room_path_.clear(); + player_->switchBorders(); + spawn_data_ = player_->getSpawnParams(); + + // Iniciar transición animada + transitioning_ = true; + transition_timer_ = 0.0F; + } else { + // changeRoom falló, limpiar + transition_old_room_.reset(); transition_direction_ = Room::Border::NONE; - // La cámara aún no está en 0, así que mantenemos transitioning_ = true - // Se resolverá cuando la cámara llegue a 0 y el jugador esté dentro de bounds - if (std::abs(camera_offset_x_) < 1.0F && std::abs(camera_offset_y_) < 1.0F) { - endTransition(); + if (BORDER == Room::Border::BOTTOM) { + killPlayer(); } } - return; - } - - // No hay transición activa — iniciar una nueva - const auto ROOM_NAME = room_->getRoom(BORDER); - - // Si no hay habitación adyacente - if (ROOM_NAME == "0") { - if (BORDER == Room::Border::BOTTOM) { - killPlayer(); - } - return; - } - - // Cargar room adyacente - auto adjacent_room = std::make_shared(ROOM_NAME, scoreboard_data_); - transition_adjacent_room_ = adjacent_room; - transition_adjacent_room_path_ = ROOM_NAME; - transition_direction_ = BORDER; - - // Pasar la room adyacente al player para colisiones - player_->setAdjacentRoom(adjacent_room, BORDER); - - // Iniciar transición (NO cambiar room_, NO reposicionar jugador) - transitioning_ = true; - - if (room_tracker_->addRoom(ROOM_NAME)) { - scoreboard_data_->rooms++; - Options::stats.rooms = scoreboard_data_->rooms; - } -} - -// Actualiza la cámara durante la transición: sigue al jugador con inercia -void Game::updateTransitionCamera(float delta_time) { - // El target es una pantalla completa en la dirección de la transición, - // excepto si el jugador ha vuelto dentro de bounds (target = 0) - float target_x = 0.0F; - float target_y = 0.0F; - - const auto RECT = player_->getRect(); - const float CENTER_X = RECT.x + (RECT.w / 2.0F); - const float CENTER_Y = RECT.y + (RECT.h / 2.0F); - const bool PLAYER_OUT_OF_BOUNDS = - CENTER_X < PlayArea::LEFT || CENTER_X > PlayArea::RIGHT || - CENTER_Y < PlayArea::TOP || CENTER_Y > PlayArea::BOTTOM; - - if (PLAYER_OUT_OF_BOUNDS) { - // El jugador está fuera: la cámara se dirige a mostrar la room adyacente - switch (transition_direction_) { - case Room::Border::TOP: target_y = static_cast(PlayArea::HEIGHT); break; - case Room::Border::BOTTOM: target_y = -static_cast(PlayArea::HEIGHT); break; - case Room::Border::LEFT: target_x = static_cast(PlayArea::WIDTH); break; - case Room::Border::RIGHT: target_x = -static_cast(PlayArea::WIDTH); break; - default: break; - } - } - // Si el jugador está dentro de bounds, target = 0 → la cámara vuelve - - // Interpolar la cámara hacia el objetivo con velocidad constante - constexpr float CAMERA_SPEED = 500.0F; - auto lerp_towards = [delta_time](float current, float target) -> float { - const float DIFF = target - current; - if (std::abs(DIFF) < 1.0F) { return target; } - const float STEP = CAMERA_SPEED * delta_time; - if (std::abs(DIFF) <= STEP) { return target; } - return current + (DIFF > 0.0F ? STEP : -STEP); - }; - - camera_offset_x_ = lerp_towards(camera_offset_x_, target_x); - camera_offset_y_ = lerp_towards(camera_offset_y_, target_y); - - // Si no hay room adyacente pendiente, la cámara vuelve a 0, y al llegar terminamos - if (!transition_adjacent_room_ && - std::abs(camera_offset_x_) < 1.0F && std::abs(camera_offset_y_) < 1.0F) { - endTransition(); } } // Finaliza la transición entre pantallas void Game::endTransition() { transitioning_ = false; - camera_offset_x_ = 0.0F; - camera_offset_y_ = 0.0F; - player_->clearAdjacentRoom(); - transition_adjacent_room_.reset(); - transition_adjacent_room_path_.clear(); + transition_just_ended_ = true; + transition_timer_ = 0.0F; + transition_old_room_.reset(); transition_direction_ = Room::Border::NONE; + player_->clearAdjacentRoom(); Screen::get()->setRenderOffset(0, 0); } diff --git a/source/game/scenes/game.hpp b/source/game/scenes/game.hpp index 5683513..8e1ba3e 100644 --- a/source/game/scenes/game.hpp +++ b/source/game/scenes/game.hpp @@ -45,6 +45,7 @@ class Game { static constexpr float DEMO_ROOM_DURATION = 6.0F; // Duración de cada habitación en modo demo en segundos (400 frames) static constexpr float FADE_STEP_INTERVAL = 0.05F; // Intervalo entre pasos de fade en segundos static constexpr float POST_FADE_DELAY = 2.0F; // Duración de la pantalla negra después del fade + static constexpr float TRANSITION_DURATION = 0.5F; // Duración de la transición entre pantallas en segundos // --- Estructuras --- struct DemoData { @@ -77,7 +78,6 @@ class Game { void togglePause(); // Pone el juego en pausa void initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr room); // Inicializa al jugador void endTransition(); // Finaliza la transición entre pantallas - void updateTransitionCamera(float delta_time); // Actualiza la cámara durante la transición void keepMusicPlaying(); // Hace sonar la música void demoInit(); // DEMO MODE: Inicializa las variables para el modo demo void demoCheckRoomChange(float delta_time); // DEMO MODE: Comprueba si se ha de cambiar de habitación @@ -108,11 +108,10 @@ class Game { // Transición animada entre pantallas bool transitioning_{false}; // Indica si hay una transición en curso - std::shared_ptr transition_adjacent_room_; // Room adyacente durante la transición - std::string transition_adjacent_room_path_; // Path de la room adyacente + float transition_timer_{0.0F}; // Tiempo transcurrido en la transición + std::shared_ptr transition_old_room_; // Habitación saliente (se mantiene viva durante la transición) Room::Border transition_direction_{Room::Border::NONE}; // Dirección de la transición - float camera_offset_x_{0.0F}; // Offset actual de la cámara (pixeles) - float camera_offset_y_{0.0F}; // Offset actual de la cámara (pixeles) + bool transition_just_ended_{false}; // Cooldown de 1 frame tras finalizar transición // Variables de demo mode DemoData demo_; // Variables para el modo demo