From 36fc496226c0d5d57422ed421d99ca91e83905fa Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 6 May 2026 14:04:27 +0200 Subject: [PATCH] =?UTF-8?q?feat(title):=20parpadeig=20STARTING=20amb=20cic?= =?UTF-8?q?le=2050/50=20i=20per=C3=ADode=20configurable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/ui.yaml | 5 +++- source/core/ui/ui_config.cpp | 4 ++-- source/core/ui/ui_config.hpp | 4 ++-- source/game/scenes/title_scene.cpp | 37 +++++++++++++++++++----------- source/utils/defaults.hpp | 8 ++++--- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/data/ui.yaml b/data/ui.yaml index 049df67..e1ed332 100644 --- a/data/ui.yaml +++ b/data/ui.yaml @@ -119,7 +119,10 @@ title: # de freqüència). Si l'altre jugador prem START dins d'aquesta # finestra, el comptador es reseteja i s'arrenca amb BOTH. starting_ms: 3000 - starting_blink_speedup: 2.0 + # Durant STARTING el prompt commuta a un cicle simètric 50/50 + # (visible/ocult) amb aquest període total. 200 ms = 5 Hz, prou + # ràpid per llegir-se com a "pulsació confirmada". + starting_blink_period_ms: 200 # Fade-out estil "mosaic dissolve" durant l'última part de STARTING. # `fade_out_ms` ha de ser <= starting_ms (els últims X ms del STARTING # es fan amb el dissolve actiu fins a quedar negre). `fade_out_max_block` diff --git a/source/core/ui/ui_config.cpp b/source/core/ui/ui_config.cpp index 60d73f4..bd421a6 100644 --- a/source/core/ui/ui_config.cpp +++ b/source/core/ui/ui_config.cpp @@ -103,7 +103,7 @@ UiConfig::UiConfig() title_blink_visible_ms_(Defaults::UI::Title::BLINK_VISIBLE_MS), title_blink_hidden_ms_(Defaults::UI::Title::BLINK_HIDDEN_MS), title_starting_ms_(Defaults::UI::Title::STARTING_MS), - title_starting_blink_speedup_(Defaults::UI::Title::STARTING_BLINK_SPEEDUP), + title_starting_blink_period_ms_(Defaults::UI::Title::STARTING_BLINK_PERIOD_MS), title_fade_out_ms_(Defaults::UI::Title::FADE_OUT_MS), title_fade_out_max_block_(Defaults::UI::Title::FADE_OUT_MAX_BLOCK), title_heat_haze_{ @@ -229,7 +229,7 @@ void UiConfig::load(const std::string& file_path) { readField(t, "blink_visible_ms", title_blink_visible_ms_); readField(t, "blink_hidden_ms", title_blink_hidden_ms_); readField(t, "starting_ms", title_starting_ms_); - readField(t, "starting_blink_speedup", title_starting_blink_speedup_); + readField(t, "starting_blink_period_ms", title_starting_blink_period_ms_); readField(t, "fade_out_ms", title_fade_out_ms_); readField(t, "fade_out_max_block", title_fade_out_max_block_); if (t.contains("heat_haze")) { diff --git a/source/core/ui/ui_config.hpp b/source/core/ui/ui_config.hpp index 2c32e28..6677909 100644 --- a/source/core/ui/ui_config.hpp +++ b/source/core/ui/ui_config.hpp @@ -96,7 +96,7 @@ class UiConfig { [[nodiscard]] auto titleBlinkVisibleMs() const -> int { return title_blink_visible_ms_; } [[nodiscard]] auto titleBlinkHiddenMs() const -> int { return title_blink_hidden_ms_; } [[nodiscard]] auto titleStartingMs() const -> int { return title_starting_ms_; } - [[nodiscard]] auto titleStartingBlinkSpeedup() const -> float { return title_starting_blink_speedup_; } + [[nodiscard]] auto titleStartingBlinkPeriodMs() const -> int { return title_starting_blink_period_ms_; } [[nodiscard]] auto titleFadeOutMs() const -> int { return title_fade_out_ms_; } [[nodiscard]] auto titleFadeOutMaxBlock() const -> int { return title_fade_out_max_block_; } [[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; } @@ -172,7 +172,7 @@ class UiConfig { int title_blink_visible_ms_; int title_blink_hidden_ms_; int title_starting_ms_; - float title_starting_blink_speedup_; + int title_starting_blink_period_ms_; int title_fade_out_ms_; int title_fade_out_max_block_; TitleHeatHaze title_heat_haze_; diff --git a/source/game/scenes/title_scene.cpp b/source/game/scenes/title_scene.cpp index 22e4102..58acebf 100644 --- a/source/game/scenes/title_scene.cpp +++ b/source/game/scenes/title_scene.cpp @@ -103,13 +103,12 @@ void TitleScene::update(int delta_ms) { if (heat_haze_) { heat_haze_->tick(delta_ms); } - // Avanç del comptador del parpadeig: durant STARTING, accelera per - // `starting_blink_speedup` (típicament 2.0). Es manté en enter perquè - // tota la lògica del cicle (visible_ms / hidden_ms) treballa amb int. - const float BLINK_RATE = (phase_ == Phase::STARTING) - ? UiConfig::get().titleStartingBlinkSpeedup() - : 1.0F; - blink_elapsed_ms_ += static_cast(static_cast(delta_ms) * BLINK_RATE); + // 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 + // defecte); STARTING usa un cicle simètric 50/50 amb període + // `starting_blink_period_ms`. + blink_elapsed_ms_ += delta_ms; const bool P1_START = pressedStart(0); const bool P2_START = pressedStart(1); @@ -169,6 +168,10 @@ void TitleScene::enterStarting(DebugConfig::StartingPlayers players) { phase_ = Phase::STARTING; pending_players_ = players; starting_timer_ms_ = UiConfig::get().titleStartingMs(); + // Resetem el comptador del parpadeig perquè el primer frame de + // STARTING comenci visible — sense això, el modulus amb el nou + // període podria caure mig cicle i donar un primer "off" estrany. + blink_elapsed_ms_ = 0; std::cout << "[scene] " << SCENE_NAME << " STARTING (players=" << DebugConfig::toString(players) << ")\n"; } @@ -221,12 +224,20 @@ void TitleScene::render() { } if (condensed_font_) { - // Cicle de parpadeig: visible_ms ON + hidden_ms OFF, repetint-se. - // Si la suma val 0 (configuració degenerada), cau a "sempre visible". - const int VISIBLE_MS = std::max(0, ui_config.titleBlinkVisibleMs()); - const int HIDDEN_MS = std::max(0, ui_config.titleBlinkHiddenMs()); - const int CYCLE_MS = VISIBLE_MS + HIDDEN_MS; - const bool VISIBLE = (CYCLE_MS == 0) || ((blink_elapsed_ms_ % CYCLE_MS) < VISIBLE_MS); + // Cicle de parpadeig: a IDLE pren visible_ms+hidden_ms del yaml + // (asimètric, 80/20 per defecte); a STARTING commuta a 50/50 amb + // `starting_blink_period_ms` per donar feedback de "polsat". + // Si el cicle val 0 (configuració degenerada), cau a "sempre visible". + int cycle_ms = 0; + int visible_ms = 0; + if (phase_ == Phase::STARTING) { + cycle_ms = std::max(0, ui_config.titleStartingBlinkPeriodMs()); + visible_ms = cycle_ms / 2; + } else { + visible_ms = std::max(0, ui_config.titleBlinkVisibleMs()); + cycle_ms = visible_ms + std::max(0, ui_config.titleBlinkHiddenMs()); + } + const bool VISIBLE = (cycle_ms == 0) || ((blink_elapsed_ms_ % cycle_ms) < visible_ms); if (VISIBLE) { const int PROMPT_Y = (GameCanvas::HEIGHT * PROMPT_Y_PERCENT) / 100; diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index 289083a..1db3597 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -261,10 +261,12 @@ namespace Defaults { // Fase STARTING: durada fixa entre la primera pulsació de // START i la transició a la següent escena. Durant aquesta - // fase, el parpadeig del prompt va a `BLINK_SPEEDUP` × la - // freqüència normal (2.0 = doble; 1.5 = un 50% més ràpid). + // fase, el parpadeig del prompt commuta a un cicle simètric + // 50/50 (visible/ocult) amb període `STARTING_BLINK_PERIOD_MS`, + // pensat per a un ritme molt més ràpid que l'animació + // atràctil del IDLE (200 ms = 5 Hz). constexpr int STARTING_MS = 3000; - constexpr float STARTING_BLINK_SPEEDUP = 2.0F; + constexpr int STARTING_BLINK_PERIOD_MS = 200; // Fade-out estil mosaic dissolve dins l'última part de la // fase STARTING. `FADE_OUT_MS` ha de ser <= STARTING_MS;