diff --git a/source/game/scene_manager.hpp b/source/game/scene_manager.hpp index fd240bef..d59618dd 100644 --- a/source/game/scene_manager.hpp +++ b/source/game/scene_manager.hpp @@ -34,7 +34,7 @@ enum class Options { // --- Variables de estado globales --- #ifdef _DEBUG -inline Scene current = Scene::CREDITS; // Escena actual +inline Scene current = Scene::ENDING; // Escena actual inline Options options = Options::LOGO_TO_LOADING_SCREEN; // Opciones de la escena actual #else inline Scene current = Scene::LOGO; // Escena actual diff --git a/source/game/scenes/ending.cpp b/source/game/scenes/ending.cpp index af4299e5..6f7336c8 100644 --- a/source/game/scenes/ending.cpp +++ b/source/game/scenes/ending.cpp @@ -10,20 +10,17 @@ #include "core/rendering/surface_sprite.hpp" // Para SSprite #include "core/rendering/text.hpp" // Para Text, TEXT_STROKE #include "core/resources/resource.hpp" // Para Resource +#include "core/system/global_events.hpp" // Para check #include "external/jail_audio.h" // Para JA_SetVolume, JA_PlayMusic, JA_StopMusic #include "game/options.hpp" // Para Options, options, OptionsGame, SectionS... #include "game/scene_manager.hpp" // Para SceneManager #include "utils/defines.hpp" // Para GAME_SPEED -#include "core/system/global_events.hpp" // Para check +#include "utils/delta_timer.hpp" // Para DeltaTimer #include "utils/utils.hpp" // Para PaletteColor // Constructor Ending::Ending() - : counter_(-1), - pre_counter_(0), - cover_counter_(0), - ticks_(0), - current_scene_(0) { + : delta_timer_(std::make_unique()) { SceneManager::current = SceneManager::Scene::ENDING; SceneManager::options = SceneManager::Options::NONE; @@ -48,29 +45,26 @@ Ending::Ending() // Actualiza el objeto void Ending::update() { - // Comprueba que la diferencia de ticks sea mayor a la velocidad del juego - if (SDL_GetTicks() - ticks_ > GAME_SPEED) { - // Actualiza el contador de ticks - ticks_ = SDL_GetTicks(); + // Obtiene el delta time + current_delta_ = delta_timer_->tick(); - // Comprueba las entradas - checkInput(); + // Comprueba las entradas + checkInput(); - // Actualiza el contador - updateCounters(); + // Actualiza el tiempo total + total_time_ += current_delta_; - // Actualiza las cortinillas de los elementos - updateSpriteCovers(); + // Actualiza la máquina de estados + updateState(current_delta_); - // Comprueba si se ha de cambiar de escena - checkChangeScene(); + // Actualiza las cortinillas de los elementos + updateSpriteCovers(); - // Actualiza el volumen de la musica - updateMusicVolume(); + // Actualiza el volumen de la musica + updateMusicVolume(); - // Actualiza el objeto Screen - Screen::get()->update(); - } + // Actualiza el objeto Screen + Screen::get()->update(current_delta_); } // Dibuja el final en pantalla @@ -81,20 +75,26 @@ void Ending::render() { // Limpia la pantalla Screen::get()->clearSurface(static_cast(PaletteColor::BLACK)); - // Dibuja las imagenes de la escena - sprite_pics_.at(current_scene_).image_sprite->render(); - sprite_pics_.at(current_scene_).cover_sprite->render(); + // Skip rendering durante WARMING_UP + if (state_ != State::WARMING_UP) { + // Dibuja las imagenes de la escena + sprite_pics_.at(current_scene_).image_sprite->render(); + sprite_pics_.at(current_scene_).cover_sprite->render(); - // Dibuja los textos de la escena - for (const auto& ti : scenes_.at(current_scene_).text_index) { - if (counter_ > ti.trigger) { - sprite_texts_.at(ti.index).image_sprite->render(); - sprite_texts_.at(ti.index).cover_sprite->render(); + // Dibuja los textos de la escena + for (const auto& ti : scenes_.at(current_scene_).text_index) { + // Convertir trigger de frames a segundos @ 60fps + const float TRIGGER_TIME = static_cast(ti.trigger) / 60.0F; + + if (state_time_ > TRIGGER_TIME) { + sprite_texts_.at(ti.index).image_sprite->render(); + sprite_texts_.at(ti.index).cover_sprite->render(); + } } - } - // Dibuja la cortinilla de cambio de escena - renderCoverTexture(); + // Dibuja la cortinilla de cambio de escena + renderCoverTexture(); + } // Vuelca el contenido del renderizador en pantalla Screen::get()->render(); @@ -113,6 +113,71 @@ void Ending::checkInput() { GlobalInputs::check(); } +// Transición entre estados +void Ending::transitionToState(State new_state) { + state_ = new_state; + state_time_ = 0.0F; + + // Al cambiar a una escena, resetear fadeout_time_ + if (new_state != State::WARMING_UP && new_state != State::ENDING) { + fadeout_time_ = 0.0F; + } +} + +// Actualiza la máquina de estados +void Ending::updateState(float delta_time) { + state_time_ += delta_time; + + switch (state_) { + case State::WARMING_UP: + if (state_time_ >= WARMUP_DURATION) { + transitionToState(State::SCENE_0); + current_scene_ = 0; + } + break; + + case State::SCENE_0: + checkChangeScene(); + // Actualizar fadeout_time_ si estamos cerca del final + if (state_time_ >= SCENE_0_DURATION - FADEOUT_START_OFFSET) { + fadeout_time_ += delta_time; + } + break; + + case State::SCENE_1: + checkChangeScene(); + if (state_time_ >= SCENE_1_DURATION - FADEOUT_START_OFFSET) { + fadeout_time_ += delta_time; + } + break; + + case State::SCENE_2: + checkChangeScene(); + if (state_time_ >= SCENE_2_DURATION - FADEOUT_START_OFFSET) { + fadeout_time_ += delta_time; + } + break; + + case State::SCENE_3: + checkChangeScene(); + if (state_time_ >= SCENE_3_DURATION - FADEOUT_START_OFFSET) { + fadeout_time_ += delta_time; + } + break; + + case State::SCENE_4: + checkChangeScene(); + if (state_time_ >= SCENE_4_DURATION - FADEOUT_START_OFFSET) { + fadeout_time_ += delta_time; + } + break; + + case State::ENDING: + // Transición a ENDING2 + break; + } +} + // Inicializa los textos void Ending::iniTexts() { // Vector con los textos @@ -370,63 +435,119 @@ void Ending::run() { JA_SetVolume(128); } -// Actualiza los contadores -void Ending::updateCounters() { - // Incrementa el contador - if (pre_counter_ < 200) { - pre_counter_++; - } else { - counter_++; - } - - if (counter_ > scenes_[current_scene_].counter_end - 100) { - cover_counter_++; - } -} - // Actualiza las cortinillas de los elementos void Ending::updateSpriteCovers() { + // Skip durante WARMING_UP + if (state_ == State::WARMING_UP) { + return; + } + // Actualiza la cortinilla de los textos - if (counter_ % 4 == 0) { - for (auto ti : scenes_.at(current_scene_).text_index) { - if (counter_ > ti.trigger) { - if (sprite_texts_.at(ti.index).cover_clip_desp > 0) { - sprite_texts_.at(ti.index).cover_clip_desp -= 2; - } else if (sprite_texts_.at(ti.index).cover_clip_height > 0) { - sprite_texts_.at(ti.index).cover_clip_height -= 2; - sprite_texts_.at(ti.index).cover_sprite->setY(sprite_texts_.at(ti.index).cover_sprite->getY() + 2); - } - sprite_texts_.at(ti.index).cover_sprite->setClip(0, sprite_texts_.at(ti.index).cover_clip_desp, sprite_texts_.at(ti.index).cover_sprite->getWidth(), sprite_texts_.at(ti.index).cover_clip_height); + for (const auto& ti : scenes_.at(current_scene_).text_index) { + // Convertir trigger de frames a segundos @ 60fps + const float TRIGGER_TIME = static_cast(ti.trigger) / 60.0F; + + if (state_time_ > TRIGGER_TIME) { + // Tiempo transcurrido desde que se activó el trigger + const float TIME_SINCE_TRIGGER = state_time_ - TRIGGER_TIME; + + // Píxeles revelados: tiempo * velocidad + const float PIXELS_REVEALED = TIME_SINCE_TRIGGER * TEXT_REVEAL_SPEED; + + // Obtener altura inicial de la superficie + const float INITIAL_HEIGHT = sprite_texts_.at(ti.index).image_surface->getHeight(); + const float Y_INITIAL = sprite_texts_.at(ti.index).image_sprite->getY(); + + // Fase 1: Revelar malla decorativa (8 píxeles) + if (PIXELS_REVEALED < 8.0F) { + sprite_texts_.at(ti.index).cover_clip_desp = static_cast(8.0F - PIXELS_REVEALED); + sprite_texts_.at(ti.index).cover_clip_height = static_cast(INITIAL_HEIGHT); + sprite_texts_.at(ti.index).cover_sprite->setY(Y_INITIAL); } + // Fase 2: Revelar contenido + else { + sprite_texts_.at(ti.index).cover_clip_desp = 0; + const float CONTENT_PIXELS = PIXELS_REVEALED - 8.0F; + sprite_texts_.at(ti.index).cover_clip_height = std::max(0, static_cast(INITIAL_HEIGHT - CONTENT_PIXELS)); + sprite_texts_.at(ti.index).cover_sprite->setY(Y_INITIAL + static_cast(CONTENT_PIXELS)); + } + + sprite_texts_.at(ti.index).cover_sprite->setClip( + 0, + sprite_texts_.at(ti.index).cover_clip_desp, + sprite_texts_.at(ti.index).cover_sprite->getWidth(), + sprite_texts_.at(ti.index).cover_clip_height + ); } } - // Actualiza la cortinilla de las imágenes - if (counter_ % 2 == 0) { - if (sprite_pics_.at(current_scene_).cover_clip_desp > 0) { - sprite_pics_.at(current_scene_).cover_clip_desp -= 2; - } else if (sprite_pics_.at(current_scene_).cover_clip_height > 0) { - sprite_pics_.at(current_scene_).cover_clip_height -= 2; - sprite_pics_.at(current_scene_).cover_clip_height = std::max(sprite_pics_.at(current_scene_).cover_clip_height, 0); - sprite_pics_.at(current_scene_).cover_sprite->setY(sprite_pics_.at(current_scene_).cover_sprite->getY() + 2); - } - sprite_pics_.at(current_scene_).cover_sprite->setClip(0, sprite_pics_.at(current_scene_).cover_clip_desp, sprite_pics_.at(current_scene_).cover_sprite->getWidth(), sprite_pics_.at(current_scene_).cover_clip_height); + // Actualiza la cortinilla de las imágenes (revelación continua desde el inicio de la escena) + const float PIXELS_REVEALED = state_time_ * IMAGE_REVEAL_SPEED; + const float INITIAL_HEIGHT = sprite_pics_.at(current_scene_).image_surface->getHeight(); + const float Y_INITIAL = sprite_pics_.at(current_scene_).image_sprite->getY(); + + // Fase 1: Revelar malla decorativa (8 píxeles) + if (PIXELS_REVEALED < 8.0F) { + sprite_pics_.at(current_scene_).cover_clip_desp = static_cast(8.0F - PIXELS_REVEALED); + sprite_pics_.at(current_scene_).cover_clip_height = static_cast(INITIAL_HEIGHT); + sprite_pics_.at(current_scene_).cover_sprite->setY(Y_INITIAL); } + // Fase 2: Revelar contenido + else { + sprite_pics_.at(current_scene_).cover_clip_desp = 0; + const float CONTENT_PIXELS = PIXELS_REVEALED - 8.0F; + sprite_pics_.at(current_scene_).cover_clip_height = std::max(0, static_cast(INITIAL_HEIGHT - CONTENT_PIXELS)); + sprite_pics_.at(current_scene_).cover_sprite->setY(Y_INITIAL + static_cast(CONTENT_PIXELS)); + } + + sprite_pics_.at(current_scene_).cover_sprite->setClip( + 0, + sprite_pics_.at(current_scene_).cover_clip_desp, + sprite_pics_.at(current_scene_).cover_sprite->getWidth(), + sprite_pics_.at(current_scene_).cover_clip_height + ); } // Comprueba si se ha de cambiar de escena void Ending::checkChangeScene() { - if (counter_ > scenes_[current_scene_].counter_end) { - current_scene_++; - counter_ = 0; - cover_counter_ = 0; - if (current_scene_ == 5) { + // Obtener duración de la escena actual + float CURRENT_DURATION = 0.0F; + State NEXT_STATE = State::ENDING; + + switch (state_) { + case State::SCENE_0: + CURRENT_DURATION = SCENE_0_DURATION; + NEXT_STATE = State::SCENE_1; + break; + case State::SCENE_1: + CURRENT_DURATION = SCENE_1_DURATION; + NEXT_STATE = State::SCENE_2; + break; + case State::SCENE_2: + CURRENT_DURATION = SCENE_2_DURATION; + NEXT_STATE = State::SCENE_3; + break; + case State::SCENE_3: + CURRENT_DURATION = SCENE_3_DURATION; + NEXT_STATE = State::SCENE_4; + break; + case State::SCENE_4: + CURRENT_DURATION = SCENE_4_DURATION; + NEXT_STATE = State::ENDING; + break; + default: + return; + } + + // Comprobar si ha pasado la duración de la escena + if (state_time_ >= CURRENT_DURATION) { + if (NEXT_STATE == State::ENDING) { // Termina el bucle SceneManager::current = SceneManager::Scene::ENDING2; - - // Mantiene los valores anteriores - current_scene_ = 4; - cover_counter_ = 100; + } else { + // Transición a la siguiente escena + current_scene_++; + transitionToState(NEXT_STATE); } } } @@ -460,20 +581,23 @@ void Ending::fillCoverTexture() { // Dibuja la cortinilla de cambio de escena void Ending::renderCoverTexture() { - if (cover_counter_ > 0) { - // Dibuja la textura que cubre el texto - const int OFFSET = std::min(cover_counter_, 100); - SDL_FRect src_rect = {0.0F, 200.0F - (cover_counter_ * 2.0F), 256.0F, OFFSET * 2.0F}; - SDL_FRect dst_rect = {0.0F, 0.0F, 256.0F, OFFSET * 2.0F}; + if (fadeout_time_ > 0.0F) { + // Convertir fadeout_time_ a equivalente de cover_counter_ @ 60fps + const float FADEOUT_COUNTER = std::min(fadeout_time_ * 60.0F, 100.0F); + + SDL_FRect src_rect = {0.0F, 200.0F - (FADEOUT_COUNTER * 2.0F), 256.0F, FADEOUT_COUNTER * 2.0F}; + SDL_FRect dst_rect = {0.0F, 0.0F, 256.0F, FADEOUT_COUNTER * 2.0F}; cover_surface_->render(&src_rect, &dst_rect); } } // Actualiza el volumen de la musica void Ending::updateMusicVolume() const { - if (current_scene_ == 4 && cover_counter_ > 0) { - const float STEP = (100.0F - cover_counter_) / 100.0F; - const int VOLUME = 128 * STEP; + if (state_ == State::SCENE_4 && fadeout_time_ > 0.0F) { + // Convertir fadeout_time_ a equivalente de cover_counter_ @ 60fps + const float FADEOUT_COUNTER = std::min(fadeout_time_ * 60.0F, 100.0F); + const float STEP = (100.0F - FADEOUT_COUNTER) / 100.0F; + const int VOLUME = static_cast(128.0F * STEP); JA_SetVolume(VOLUME); } } \ No newline at end of file diff --git a/source/game/scenes/ending.hpp b/source/game/scenes/ending.hpp index d1cc1a84..fa78a76a 100644 --- a/source/game/scenes/ending.hpp +++ b/source/game/scenes/ending.hpp @@ -7,6 +7,7 @@ #include // Para vector class SurfaceSprite; // lines 8-8 class Surface; // lines 9-9 +class DeltaTimer; class Ending { public: @@ -18,6 +19,30 @@ class Ending { void run(); private: + // --- Estados --- + enum class State { + WARMING_UP, + SCENE_0, + SCENE_1, + SCENE_2, + SCENE_3, + SCENE_4, + ENDING + }; + + // --- Constantes de tiempo (basado en 60 FPS) --- + static constexpr float WARMUP_DURATION = 3.333F; // 200 frames @ 60fps + static constexpr float FADEOUT_DURATION = 1.667F; // 100 frames @ 60fps + static constexpr float SCENE_0_DURATION = 16.667F; // 1000 frames @ 60fps + static constexpr float SCENE_1_DURATION = 23.333F; // 1400 frames @ 60fps + static constexpr float SCENE_2_DURATION = 16.667F; // 1000 frames @ 60fps + static constexpr float SCENE_3_DURATION = 13.333F; // 800 frames @ 60fps + static constexpr float SCENE_4_DURATION = 16.667F; // 1000 frames @ 60fps + static constexpr float TEXT_REVEAL_SPEED = 30.0F; // 2px cada 4 frames @ 60fps + static constexpr float IMAGE_REVEAL_SPEED = 60.0F; // 2px cada 2 frames @ 60fps + static constexpr float TEXT_LAPSE = 1.333F; // 80 frames @ 60fps + static constexpr float FADEOUT_START_OFFSET = 1.667F; // Inicio fade-out 100 frames antes del fin + // --- Estructuras --- struct EndingSurface // Estructura con dos texturas y sprites, uno para mostrar y el otro hace de cortinilla { @@ -51,27 +76,30 @@ class Ending { std::shared_ptr cover_surface_; // Surface para cubrir el texto // --- Variables --- - int counter_; // Contador - int pre_counter_; // Contador previo - int cover_counter_; // Contador para la cortinilla - Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa + std::unique_ptr delta_timer_; // Temporizador delta para time-based update + State state_ = State::WARMING_UP; // Estado actual + float state_time_ = 0.0F; // Tiempo acumulado en el estado actual + float total_time_ = 0.0F; // Tiempo total acumulado desde el inicio + float fadeout_time_ = 0.0F; // Tiempo acumulado para el fade-out + float current_delta_ = 0.0F; // Delta time del frame actual std::vector sprite_texts_; // Vector con los sprites de texto con su cortinilla std::vector sprite_pics_; // Vector con los sprites de texto con su cortinilla - int current_scene_; // Escena actual + int current_scene_ = 0; // Escena actual (0-4) std::vector scenes_; // Vector con los textos e imagenes de cada escena // --- Funciones --- - void update(); // Actualiza el objeto - void render(); // Dibuja el final en pantalla - static void checkEvents(); // Comprueba el manejador de eventos - static void checkInput(); // Comprueba las entradas - void iniTexts(); // Inicializa los textos - void iniPics(); // Inicializa las imagenes - void iniScenes(); // Inicializa las escenas - void updateCounters(); // Actualiza los contadores - void updateSpriteCovers(); // Actualiza las cortinillas de los elementos - void checkChangeScene(); // Comprueba si se ha de cambiar de escena - void fillCoverTexture(); // Rellena la textura para la cortinilla - void renderCoverTexture(); // Dibuja la cortinilla de cambio de escena - void updateMusicVolume() const; // Actualiza el volumen de la musica + void update(); // Actualiza el objeto + void render(); // Dibuja el final en pantalla + static void checkEvents(); // Comprueba el manejador de eventos + static void checkInput(); // Comprueba las entradas + void iniTexts(); // Inicializa los textos + void iniPics(); // Inicializa las imagenes + void iniScenes(); // Inicializa las escenas + void updateState(float delta_time); // Actualiza la máquina de estados + void transitionToState(State new_state); // Transición entre estados + void updateSpriteCovers(); // Actualiza las cortinillas de los elementos + void checkChangeScene(); // Comprueba si se ha de cambiar de escena + void fillCoverTexture(); // Rellena la textura para la cortinilla + void renderCoverTexture(); // Dibuja la cortinilla de cambio de escena + void updateMusicVolume() const; // Actualiza el volumen de la musica }; \ No newline at end of file