#pragma once #include #include #include #include "core/rendering/effects/heat_haze.hpp" #include "core/rendering/transitions/fade_to_black.hpp" #include "core/rendering/transitions/mosaic.hpp" #include "core/scene/scene.hpp" #include "core/ui/ui_config.hpp" #include "game/debug_config.hpp" class Surface; class Text; // Escena TITLE provisional. Pinta dos blocs de text sobre un fons gairebé // negre i un prompt "press start" intermitent fins que apareix l'artwork // real. Tota la cosmètica (colors, durada, ritme del parpadeig) viu a // data/config/ui.yaml secció `title:` per poder afinar-la sense recompilar. class TitleScene : public Scene { public: // Fases de l'escena. // - IDLE: animació atràctil. Només P1 START o P2 START hi fan // transició; un timeout `title.duration_ms` porta a ATTRACT_OUT. // - STARTING: comptador fix entre la primera pulsació de START i // la transició cap a INTRO. Si l'altre jugador prem START dins // d'aquesta finestra, el comptador es reseteja amb // `pending_players_=BOTH`. Visual: mosaic dissolve + fade-out // de música. // - ATTRACT_OUT: fase de sortida silenciosa cap a LOGO quan // expira el timeout sense pulsacions. Visual: fade-to-black net. // NO toca la música ni canvia la freqüència del parpadeig. enum class Phase : std::uint8_t { IDLE, STARTING, ATTRACT_OUT }; TitleScene(std::shared_ptr condensed_font, std::shared_ptr copyright_font, std::shared_ptr background, std::shared_ptr game_logo, std::shared_ptr mini_logo); ~TitleScene() override; void update(int delta_ms) override; void render() override; private: void tickIdle(int delta_ms, bool p1_start, bool p2_start); void tickStarting(int delta_ms, bool p1_start, bool p2_start); void tickAttract(int delta_ms); void enterStarting(DebugConfig::StartingPlayers players); void enterAttract(); void advanceToIntro(DebugConfig::StartingPlayers players); void advanceToLogo(); // Helpers de `render()` extrets per evitar que la funció gran acumuli // massa branques. Cadascun pinta un bloc independent del frame. void renderBackground(); void renderGameLogo(const UiConfig::TitleIntro& intro); void renderPressStart(const UiConfig::TitleIntro& intro); void renderFooter(const UiConfig::TitleIntro& intro); void renderTransitions(); std::shared_ptr condensed_font_; std::shared_ptr copyright_font_; std::shared_ptr background_; std::shared_ptr game_logo_; std::shared_ptr mini_logo_; int timer_ms_; int blink_elapsed_ms_{0}; // Rellotge de la seqüència d'intro (vegeu `title.intro` a ui.yaml). // Avança sempre amb `delta_ms`. La visibilitat i el slide de cada // element es deriven d'ell a `render()` — no cal cap altre estat. int intro_elapsed_ms_{0}; // Detector de l'instant en què `intro_elapsed_ms_` creua per primera // vegada `press_start_at_ms`: en aquell frame es reseteja // `blink_elapsed_ms_` perquè el text aparegui en estat visible. bool press_start_armed_{false}; std::optional heat_haze_; Phase phase_{Phase::IDLE}; int starting_timer_ms_{0}; 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_; };