From 1431f27cfa1fe2dd9f5bb4c0efdeb84fa23765c8 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 6 May 2026 19:34:59 +0200 Subject: [PATCH] feat(title): fade-in inicial de negre a escena (500 ms, configurable a ui.yaml) --- data/ui.yaml | 4 ++++ source/core/ui/ui_config.cpp | 2 ++ source/core/ui/ui_config.hpp | 2 ++ source/game/scenes/title_scene.cpp | 16 ++++++++++++++++ source/game/scenes/title_scene.hpp | 5 +++++ source/utils/defaults.hpp | 7 +++++++ 6 files changed, 36 insertions(+) diff --git a/data/ui.yaml b/data/ui.yaml index ef52b96..1fa63b0 100644 --- a/data/ui.yaml +++ b/data/ui.yaml @@ -139,6 +139,10 @@ title: # mosaic — capa negra lineal damunt del canvas. La música no es toca: # `title.ogg` continua sonant cap a LOGO, que ja veurà què en fa. attract_fade_to_black_ms: 800 + # Fade-in inicial: capa negra al començament que s'esvaeix cap al fons. + # Mateix valor que `room_intro.fade_in_ms` (data/scenes/game.yaml) perquè + # l'arribada a la TITLE i a una sala de joc tinguin el mateix ritme. + intro_fade_in_ms: 500 # Distorsió sinusoidal del fons (efecte de calor del desert). # `amplitude_px`: desplaçament màxim de cada fila en píxels. # `wavelength_px`: distància vertical entre dos pics de l'ona. diff --git a/source/core/ui/ui_config.cpp b/source/core/ui/ui_config.cpp index e8a63af..536c37e 100644 --- a/source/core/ui/ui_config.cpp +++ b/source/core/ui/ui_config.cpp @@ -108,6 +108,7 @@ UiConfig::UiConfig() title_fade_out_max_block_(Defaults::UI::Title::FADE_OUT_MAX_BLOCK), title_music_fade_out_ms_(Defaults::UI::Title::MUSIC_FADE_OUT_MS), title_attract_fade_to_black_ms_(Defaults::UI::Title::ATTRACT_FADE_TO_BLACK_MS), + title_intro_fade_in_ms_(Defaults::UI::Title::INTRO_FADE_IN_MS), title_heat_haze_{ .enabled = Defaults::UI::Title::HeatHaze::ENABLED, .amplitude_px = Defaults::UI::Title::HeatHaze::AMPLITUDE_PX, @@ -238,6 +239,7 @@ void UiConfig::load(const std::string& file_path) { readField(t, "fade_out_max_block", title_fade_out_max_block_); readField(t, "music_fade_out_ms", title_music_fade_out_ms_); readField(t, "attract_fade_to_black_ms", title_attract_fade_to_black_ms_); + readField(t, "intro_fade_in_ms", title_intro_fade_in_ms_); if (t.contains("heat_haze")) { const auto& hh = t["heat_haze"]; readField(hh, "enabled", title_heat_haze_.enabled); diff --git a/source/core/ui/ui_config.hpp b/source/core/ui/ui_config.hpp index 98c8cc8..193facf 100644 --- a/source/core/ui/ui_config.hpp +++ b/source/core/ui/ui_config.hpp @@ -101,6 +101,7 @@ class UiConfig { [[nodiscard]] auto titleFadeOutMaxBlock() const -> int { return title_fade_out_max_block_; } [[nodiscard]] auto titleMusicFadeOutMs() const -> int { return title_music_fade_out_ms_; } [[nodiscard]] auto titleAttractFadeToBlackMs() const -> int { return title_attract_fade_to_black_ms_; } + [[nodiscard]] auto titleIntroFadeInMs() const -> int { return title_intro_fade_in_ms_; } [[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; } // Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa @@ -180,6 +181,7 @@ class UiConfig { int title_fade_out_max_block_; int title_music_fade_out_ms_; int title_attract_fade_to_black_ms_; + int title_intro_fade_in_ms_; TitleHeatHaze title_heat_haze_; std::string menu_select_sound_; diff --git a/source/game/scenes/title_scene.cpp b/source/game/scenes/title_scene.cpp index 49c65a4..9b25de2 100644 --- a/source/game/scenes/title_scene.cpp +++ b/source/game/scenes/title_scene.cpp @@ -101,6 +101,10 @@ TitleScene::TitleScene(std::shared_ptr display_font, std::shared_ptr .max_block_size = UiConfig::get().titleFadeOutMaxBlock(), }); attract_fade_.emplace(); + // Fade-in inicial: arrenquem en negre opac i ens esvaïm cap a la + // imatge de fons. Sense lligar a cap fase — viu sempre al frame i + // un cop finished, `render()` és no-op fins que la TitleScene mor. + intro_fade_.begin(UiConfig::get().titleIntroFadeInMs(), Transitions::FadeToBlack::Direction::FADE_IN); // Música de fons en bucle. Es preloda al Director, així que la // primera crida és immediata. El fade-out arrenca en entrar a @@ -122,6 +126,11 @@ void TitleScene::update(int delta_ms) { if (heat_haze_) { heat_haze_->tick(delta_ms); } + // Fade-in inicial: tica sempre. Un cop finished, FadeToBlack::tick + // és idempotent (clamp intern al duration_ms_) i `render()` pinta + // alpha=0 (no-op) durant la resta del cicle. + intro_fade_.tick(delta_ms); + // Comptador del parpadeig: avança sempre amb `delta_ms` cru. La // lectura (cicle/visible) la decideix `render()` segons la fase: // IDLE usa visible_ms / hidden_ms del yaml (asimètric, 80/20 per @@ -336,6 +345,13 @@ void TitleScene::render() { Notifier::get().render(); + // Fade-in inicial: capa negra que s'esvaeix cap a l'escena durant + // els primers `title.intro_fade_in_ms` ms. `render()` és no-op un + // cop finished, així que es queda penjat aquí sense lògica + // condicional. Va per damunt del Notifier — coherent amb la resta + // de transicions — però per davall del RenderInfo de debug. + intro_fade_.render(); + // Capa de transició final segons la fase. Sempre cobreix l'escena // i el Notifier, mai el RenderInfo (overlay de debug, sempre // visible). STARTING usa mosaic dissolve; ATTRACT_OUT, fade-to-black diff --git a/source/game/scenes/title_scene.hpp b/source/game/scenes/title_scene.hpp index 14a12a9..61197e3 100644 --- a/source/game/scenes/title_scene.hpp +++ b/source/game/scenes/title_scene.hpp @@ -61,4 +61,9 @@ class TitleScene : public Scene { DebugConfig::StartingPlayers pending_players_{DebugConfig::StartingPlayers::BOTH}; std::optional fade_out_; std::optional attract_fade_; + // Fade-in inicial: capa negra que s'esvaeix cap a la imatge de fons + // durant els primers `title.intro_fade_in_ms` ms. Sempre activa al + // ctor; un cop finished, `render()` pinta alpha=0 (no-op) la resta + // del cicle de l'escena. + Transitions::FadeToBlack intro_fade_; }; diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index 0218a7f..413688b 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -294,6 +294,13 @@ namespace Defaults { // veurà què en fa. constexpr int ATTRACT_FADE_TO_BLACK_MS = 800; + // Fade-in inicial de l'escena: capa negra al començament que + // s'esvaeix cap a la imatge de fons. Lineal, sense mosaic — + // mateixa convenció que el `room_intro.fade_in_ms` del joc + // perquè l'arribada a la TITLE i l'arribada a una sala de + // joc tinguin el mateix ritme visual. + constexpr int INTRO_FADE_IN_MS = 500; + // Distorsió sinusoidal horitzontal de l'imatge de fons // (efecte de calor del desert). `amplitude_px` és el // desplaçament màxim per fila; `wavelength_px` la distància