diff --git a/data/config/ui.yaml b/data/config/ui.yaml index 7d8e0b2..a490fc9 100644 --- a/data/config/ui.yaml +++ b/data/config/ui.yaml @@ -73,6 +73,12 @@ hud: entry_ms: 800 # durada de l'animació d'entrada dels tres blocs flash_ms: 250 # durada del flash quan un jugador rep un hit shake_ms: 300 # durada del shake que acompanya el flash + # Parpadeig dels prompts inactius ("Polsa start..." / "Espera..."). Un + # únic temporitzador global al Hud sincronitza P1 i P2 i no es reinicia + # quan el text canvia entre CAN_JOIN i WAIT. cycle<=0 o ratio>=1 → + # sempre visible (disable trivial). + blink_cycle_ms: 1000 # durada del cicle complet + blink_visible_ratio: 0.8 # proporció del cicle visible (0..1) # Famílies de color del HUD per bloc. Cada bloc té `label` (to apagat # per etiquetes), `value` (to viu per a la xifra) i `shadow` (to fosc diff --git a/source/core/ui/ui_config.cpp b/source/core/ui/ui_config.cpp index 5d55ace..1fee001 100644 --- a/source/core/ui/ui_config.cpp +++ b/source/core/ui/ui_config.cpp @@ -99,6 +99,8 @@ UiConfig::UiConfig() hud_entry_ms_(Defaults::UI::HUD_ENTRY_MS), hud_flash_ms_(Defaults::UI::HUD_FLASH_MS), hud_shake_ms_(Defaults::UI::HUD_SHAKE_MS), + hud_blink_cycle_ms_(Defaults::UI::HUD_BLINK_CYCLE_MS), + hud_blink_visible_ratio_(Defaults::UI::HUD_BLINK_VISIBLE_RATIO), pause_enter_ms_(Defaults::UI::PAUSE_ENTER_MS), pause_exit_ms_(Defaults::UI::PAUSE_EXIT_MS), pause_enter_curve_(curveByName(Defaults::UI::PAUSE_ENTER_CURVE)), @@ -267,6 +269,8 @@ void UiConfig::load(const std::string& file_path) { readField(h, "entry_ms", hud_entry_ms_); readField(h, "flash_ms", hud_flash_ms_); readField(h, "shake_ms", hud_shake_ms_); + readField(h, "blink_cycle_ms", hud_blink_cycle_ms_); + readField(h, "blink_visible_ratio", hud_blink_visible_ratio_); } if (yaml.contains("pause")) { diff --git a/source/core/ui/ui_config.hpp b/source/core/ui/ui_config.hpp index 1cb687b..f2e3029 100644 --- a/source/core/ui/ui_config.hpp +++ b/source/core/ui/ui_config.hpp @@ -89,6 +89,8 @@ class UiConfig { [[nodiscard]] auto hudEntryMs() const -> int { return hud_entry_ms_; } [[nodiscard]] auto hudFlashMs() const -> int { return hud_flash_ms_; } [[nodiscard]] auto hudShakeMs() const -> int { return hud_shake_ms_; } + [[nodiscard]] auto hudBlinkCycleMs() const -> int { return hud_blink_cycle_ms_; } + [[nodiscard]] auto hudBlinkVisibleRatio() const -> float { return hud_blink_visible_ratio_; } [[nodiscard]] auto pauseEnterMs() const -> int { return pause_enter_ms_; } [[nodiscard]] auto pauseExitMs() const -> int { return pause_exit_ms_; } @@ -238,6 +240,8 @@ class UiConfig { int hud_entry_ms_; int hud_flash_ms_; int hud_shake_ms_; + int hud_blink_cycle_ms_; + float hud_blink_visible_ratio_; int pause_enter_ms_; int pause_exit_ms_; CurveFn pause_enter_curve_; diff --git a/source/game/ui/hud.cpp b/source/game/ui/hud.cpp index cf9a286..7916461 100644 --- a/source/game/ui/hud.cpp +++ b/source/game/ui/hud.cpp @@ -188,6 +188,10 @@ void Hud::update(int delta_ms) { if (b.shake_ms_left > 0) { b.shake_ms_left = std::max(0, b.shake_ms_left - delta_ms); } if (b.flash_ms_left > 0) { b.flash_ms_left = std::max(0, b.flash_ms_left - delta_ms); } } + const int CYCLE = UiConfig::get().hudBlinkCycleMs(); + if (CYCLE > 0) { + blink_elapsed_ms_ = (blink_elapsed_ms_ + delta_ms) % CYCLE; + } } void Hud::setMode(BlockId block, Mode mode) { @@ -238,6 +242,16 @@ auto Hud::computeOffset(const Block& b) -> SDL_FPoint { return off; } +auto Hud::blinkVisible() const -> bool { + const auto& ui = UiConfig::get(); + const int CYCLE = ui.hudBlinkCycleMs(); + const float RATIO = ui.hudBlinkVisibleRatio(); + if (CYCLE <= 0 || RATIO >= 1.0F) { return true; } + if (RATIO <= 0.0F) { return false; } + const int VISIBLE_MS = static_cast(static_cast(CYCLE) * RATIO); + return blink_elapsed_ms_ < VISIBLE_MS; +} + void Hud::render() const { // Backdrop de la franja del HUD. Es pinta abans dels tres blocs perquè, // quan un bloc es mou (entry slide o shake), la zona que deixa al @@ -295,6 +309,7 @@ void Hud::renderIdleSide(int x, int y, int w, const Block& blk, int player_index // el mode fixat per la GameScene cada frame. El render de zeros // queda com a fallback (mode NONE o slot actiu). if (!p.active && p.join_prompt != JoinPromptMode::NONE) { + if (!blinkVisible()) { return; } const bool CAN_JOIN = (p.join_prompt == JoinPromptMode::CAN_JOIN); const std::string L1 = Locale::get().text( CAN_JOIN ? "hud.join_prompt_can_join_l1" : "hud.join_prompt_wait_l1"); diff --git a/source/game/ui/hud.hpp b/source/game/ui/hud.hpp index c38220b..783660f 100644 --- a/source/game/ui/hud.hpp +++ b/source/game/ui/hud.hpp @@ -170,6 +170,10 @@ class Hud { // les constants de classe (no toca cap altre membre). [[nodiscard]] static auto computeOffset(const Block& b) -> SDL_FPoint; + // Estat actual del parpadeig dels prompts inactius. Llegit per + // renderIdleSide() per saltar el draw quan toca ocult. + [[nodiscard]] auto blinkVisible() const -> bool; + EventBus& bus_; std::vector subs_; @@ -179,6 +183,10 @@ class Hud { std::array players_; int pyramid_{1}; int room_{1}; + // Acumulador continu (ms) per al parpadeig dels prompts inactius. + // Es manté entre canvis de mode (CAN_JOIN ↔ WAIT) per evitar salts + // visuals i sincronitza els dos slots — vegeu Hud::blinkVisible(). + int blink_elapsed_ms_{0}; // Vides amb què s'arrenca un slot (`activatePlayer`). Cachejat al // ctor des de `Player::death_config.initial_lives` per no llegir el // singleton estàtic en el camí calent del bus de events. diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index c5cfe10..f07f7f2 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -246,6 +246,12 @@ namespace Defaults { constexpr int HUD_FLASH_MS = 250; constexpr int HUD_SHAKE_MS = 300; + // Parpadeig dels prompts inactius del HUD ("Polsa start..." / + // "Espera..."). Un únic temporitzador global al Hud sincronitza + // P1 i P2. cycle<=0 o ratio>=1 → sempre visible (disable trivial). + constexpr int HUD_BLINK_CYCLE_MS = 1000; + constexpr float HUD_BLINK_VISIBLE_RATIO = 0.8F; + // Pausa graduada de la GameScene: el món no para d'un colp, es // frena amb una corba i torna a arrancar amb una altra. Els noms // de corba han de coincidir amb els registrats a