From ef04500a44607fd9b497d0bfdf68d9e37c8e9d66 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 6 Apr 2026 18:43:59 +0200 Subject: [PATCH] canvi de pantalla amb easing --- source/core/rendering/screen.cpp | 6 ++ source/core/rendering/screen.hpp | 9 ++ source/core/rendering/surface.cpp | 24 ++++++ source/game/scenes/game.cpp | 133 ++++++++++++++++++++++++++---- source/game/scenes/game.hpp | 10 ++- 5 files changed, 164 insertions(+), 18 deletions(-) diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 3d2b24d..1c5e7ee 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -358,6 +358,12 @@ void Screen::updateZoomFactor() { zoom_factor_ = Options::video.integer_scale ? std::floor(SCALE) : SCALE; } +// Establece el offset de renderizado (para transiciones de pantalla) +void Screen::setRenderOffset(int x, int y) { + render_offset_x_ = x; + render_offset_y_ = y; +} + // Establece el renderizador para las surfaces void Screen::setRendererSurface(const std::shared_ptr& surface) { (surface) ? renderer_surface_ = std::make_shared>(surface) : renderer_surface_ = std::make_shared>(game_surface_); diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index c59a1ae..d06cc07 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -71,6 +71,11 @@ class Screen { void setActiveShader(Rendering::ShaderType type); // Cambia el shader de post-procesado activo void nextShader(); // Cicla al siguiente shader disponible (para futura UI) + // Render offset (para transiciones de pantalla) + void setRenderOffset(int x, int y); // Establece el offset de renderizado + [[nodiscard]] auto getRenderOffsetX() const -> int { return render_offset_x_; } + [[nodiscard]] auto getRenderOffsetY() const -> int { return render_offset_y_; } + // Surfaces y notificaciones void setRendererSurface(const std::shared_ptr& surface = nullptr); // Establece el renderizador para las surfaces void setNotificationsEnabled(bool value); // Establece la visibilidad de las notificaciones @@ -181,4 +186,8 @@ class Screen { // Shaders std::string info_resolution_; // Texto con la información de la pantalla std::string gpu_driver_; // Nombre del driver GPU (SDL3GPU), capturado en initShaders() + + // Render offset para transiciones + int render_offset_x_{0}; + int render_offset_y_{0}; }; \ No newline at end of file diff --git a/source/core/rendering/surface.cpp b/source/core/rendering/surface.cpp index 2548e5f..ceb8bbf 100644 --- a/source/core/rendering/surface.cpp +++ b/source/core/rendering/surface.cpp @@ -249,6 +249,10 @@ void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) { / void Surface::render(float dx, float dy, float sx, float sy, float w, float h) { // NOLINT(readability-make-member-function-const) auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); + // Aplicar render offset + dx += Screen::get()->getRenderOffsetX(); + dy += Screen::get()->getRenderOffsetY(); + // Limitar la región para evitar accesos fuera de rango en origen w = std::min(w, surface_data_->width - sx); h = std::min(h, surface_data_->height - sy); @@ -280,6 +284,10 @@ void Surface::render(float dx, float dy, float sx, float sy, float w, float h) { void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { // NOLINT(readability-make-member-function-const) auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); + // Aplicar render offset + x += Screen::get()->getRenderOffsetX(); + y += Screen::get()->getRenderOffsetY(); + // Determina la región de origen (clip) a renderizar float sx = (src_rect != nullptr) ? src_rect->x : 0; float sy = (src_rect != nullptr) ? src_rect->y : 0; @@ -352,6 +360,10 @@ void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip // Si dstRect es nullptr, asignar las mismas dimensiones que srcRect float dx = (dst_rect != nullptr) ? dst_rect->x : 0; float dy = (dst_rect != nullptr) ? dst_rect->y : 0; + + // Aplicar render offset + dx += Screen::get()->getRenderOffsetX(); + dy += Screen::get()->getRenderOffsetY(); float dw = (dst_rect != nullptr) ? dst_rect->w : sw; float dh = (dst_rect != nullptr) ? dst_rect->h : sh; @@ -392,6 +404,10 @@ void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect, SDL_FlipMode flip) const { auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); + // Aplicar render offset + x += Screen::get()->getRenderOffsetX(); + y += Screen::get()->getRenderOffsetY(); + // Determina la región de origen (clip) a renderizar float sx = (src_rect != nullptr) ? src_rect->x : 0; float sy = (src_rect != nullptr) ? src_rect->y : 0; @@ -450,6 +466,10 @@ static auto computeFadeDensity(int screen_y, int fade_h, int canvas_height) -> f // Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig) void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) const { + // Aplicar render offset + x += Screen::get()->getRenderOffsetX(); + y += Screen::get()->getRenderOffsetY(); + const int SX = (src_rect != nullptr) ? static_cast(src_rect->x) : 0; const int SY = (src_rect != nullptr) ? static_cast(src_rect->y) : 0; const int SW = (src_rect != nullptr) ? static_cast(src_rect->w) : static_cast(surface_data_->width); @@ -487,6 +507,10 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height // Idem però reemplaçant un color índex void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect) const { + // Aplicar render offset + x += Screen::get()->getRenderOffsetX(); + y += Screen::get()->getRenderOffsetY(); + const int SX = (src_rect != nullptr) ? static_cast(src_rect->x) : 0; const int SY = (src_rect != nullptr) ? static_cast(src_rect->y) : 0; const int SW = (src_rect != nullptr) ? static_cast(src_rect->w) : static_cast(surface_data_->width); diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index 418797f..12dcc4d 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -29,6 +29,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 @@ -314,6 +315,9 @@ void Game::updatePlaying(float delta_time) { // Actualiza los objetos room_->update(delta_time); + if (transitioning_) { + transition_old_room_->update(delta_time); + } switch (mode_) { case Mode::GAME: #ifdef _DEBUG @@ -333,6 +337,14 @@ void Game::updatePlaying(float delta_time) { checkIfPlayerIsAlive(); checkEndGame(); checkSomeCheevos(); + + // Avanzar transición + if (transitioning_) { + transition_timer_ += delta_time; + if (transition_timer_ >= TRANSITION_DURATION) { + endTransition(); + } + } break; case Mode::DEMO: @@ -408,6 +420,11 @@ void Game::updatePostFadeEnding(float delta_time) { // Cambia al estado especificado y resetea los timers void Game::transitionToState(State new_state) { + // Limpiar transición de pantalla si estaba activa + if (transitioning_) { + endTransition(); + } + // Lógica de ENTRADA según el nuevo estado if (new_state == State::BLACK_SCREEN) { // Respawn room y player @@ -450,25 +467,76 @@ void Game::renderPlaying() { // Prepara para dibujar el frame Screen::get()->start(); - // Dibuja el mapa de tiles (siempre) - room_->renderMap(); - #ifdef _DEBUG // Si el editor está activo, delegar el renderizado de entidades y statusbar if (MapEditor::get()->isActive()) { + room_->renderMap(); MapEditor::get()->render(); Screen::get()->render(); return; } #endif - // Dibuja los elementos del juego en orden - room_->renderEnemies(); - room_->renderItems(); - if (mode_ == Mode::GAME) { - player_->render(); + if (transitioning_) { + // --- Transición animada entre pantallas --- + const float T = std::min(transition_timer_ / TRANSITION_DURATION, 1.0F); + const float P = Easing::cubicInOut(T); + + // 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(); + if (mode_ == Mode::GAME) { + player_->render(); + } + + // Scoreboard sin offset + Screen::get()->setRenderOffset(0, 0); + scoreboard_->render(); + } else { + // --- Renderizado normal --- + room_->renderMap(); + room_->renderEnemies(); + room_->renderItems(); + if (mode_ == Mode::GAME) { + player_->render(); + } + scoreboard_->render(); } - scoreboard_->render(); #ifdef _DEBUG // Debug info @@ -733,25 +801,56 @@ auto Game::changeRoom(const std::string& room_path) -> bool { // Comprueba si el jugador esta en el borde de la pantalla void Game::checkPlayerIsOnBorder() { + // No permitir transiciones encadenadas + if (transitioning_) { + return; + } + if (player_->isOnBorder()) { const auto BORDER = player_->getBorder(); const auto ROOM_NAME = room_->getRoom(BORDER); - // Si puede cambiar de habitación, cambia - if (changeRoom(ROOM_NAME)) { - player_->switchBorders(); - spawn_data_ = player_->getSpawnParams(); + // Si no hay habitación adyacente + if (ROOM_NAME == "0") { + if (BORDER == Room::Border::BOTTOM) { + killPlayer(); + } return; } - // Si ha llegado al fondo y no hay habitación, muere - if (BORDER == Room::Border::BOTTOM) { - killPlayer(); - return; + // Guardar la habitación saliente + transition_old_room_ = room_; + transition_direction_ = BORDER; + + // Crear nueva habitación y reposicionar jugador + if (changeRoom(ROOM_NAME)) { + 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; + + if (BORDER == Room::Border::BOTTOM) { + killPlayer(); + } } } } +// Finaliza la transición entre pantallas +void Game::endTransition() { + transitioning_ = false; + transition_timer_ = 0.0F; + transition_old_room_.reset(); + transition_direction_ = Room::Border::NONE; + Screen::get()->setRenderOffset(0, 0); +} + // Comprueba las colisiones del jugador con los enemigos auto Game::checkPlayerAndEnemies() -> bool { const bool DEATH = room_->enemyCollision(player_->getCollider()); diff --git a/source/game/scenes/game.hpp b/source/game/scenes/game.hpp index 1a04322..ea69b6e 100644 --- a/source/game/scenes/game.hpp +++ b/source/game/scenes/game.hpp @@ -8,8 +8,8 @@ #include // Para vector #include "game/entities/player.hpp" // Para PlayerSpawn +#include "game/gameplay/room.hpp" // Para Room, Room::Border #include "utils/delta_timer.hpp" // Para DeltaTimer -class Room; // lines 12-12 class RoomTracker; // lines 13-13 class Scoreboard; // lines 14-14 class Surface; @@ -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 { @@ -80,6 +81,7 @@ class Game { void checkSomeCheevos(); // Comprueba algunos logros void checkEndGameCheevos(); // Comprueba los logros de completar el juego void initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr room); // Inicializa al jugador + void endTransition(); // Finaliza la transición entre pantallas 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 @@ -109,6 +111,12 @@ class Game { float state_time_{0.0F}; // Tiempo acumulado en el estado actual float fade_accumulator_{0.0F}; // Acumulador de tiempo para el fade + // Transición animada entre pantallas + bool transitioning_{false}; // Indica si hay una transición en curso + 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 + // Variables de demo mode DemoData demo_; // Variables para el modo demo