From 71ed9dc24faf74a6f7b4a8ce3d50d9a927c22efd Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Tue, 26 May 2026 19:17:22 +0200 Subject: [PATCH] feat(hud): paleta per segments (P1 blanc, vides ambre, nivell verd, P2 rosa) --- source/core/defaults/hud.hpp | 11 ++++ source/game/scenes/game_scene.cpp | 64 ++++++++++------------- source/game/scenes/game_scene.hpp | 6 ++- source/game/systems/init_hud_animator.cpp | 40 ++++++++++++-- source/game/systems/init_hud_animator.hpp | 26 +++++++-- 5 files changed, 100 insertions(+), 47 deletions(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index 4ae15e6..5ead158 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -12,6 +12,17 @@ namespace Defaults::Hud { constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; + // Colors per segment del marcador. Jerarquia per funció (score/vides/nivell) + // + diferenciació de jugador (P1 blanc vs P2 rosa) sense xocar amb els + // colors d'enemics (cyan/roig). Si alpha=255 desactiva l'oscil·lador global + // i mostra el color estable (en lloc del pulse verd genèric). + namespace Colors { + constexpr SDL_Color SCORE_P1 = {.r = 255, .g = 255, .b = 255, .a = 255}; // blanc + constexpr SDL_Color SCORE_P2 = {.r = 255, .g = 130, .b = 200, .a = 255}; // rosa magenta + constexpr SDL_Color LIVES = {.r = 255, .g = 180, .b = 60, .a = 255}; // ambre / or + constexpr SDL_Color LEVEL = {.r = 155, .g = 255, .b = 175, .a = 255}; // verd sistema + } // namespace Colors + // Animación de entrada del HUD (init_hud_animator). namespace InitAnim { // Spawn vertical de la nave: 50 px bajo la PLAYAREA (sale desde fuera). diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 5a61d40..69ff513 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -710,7 +710,7 @@ void GameScene::drawInitHudState() { } if (score_progress > 0.0F) { - Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboard(), score_progress); + Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboardSegments(), score_progress); } if (ship1_progress > 0.0F && match_config_.player1_active && ships_[0].isActive()) { @@ -816,59 +816,49 @@ void GameScene::tocado(uint8_t player_id, const Vec2& bullet_velocity) { } void GameScene::drawScoreboard() { - // Construir text del marcador - std::string text = buildScoreboard(); - - // Parámetros de renderització const float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE; const float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING; - - // Calcular centro de la zone del marcador const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD; - float center_x = scoreboard_zone.w / 2.0F; - float center_y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F); - - // Renderizar centrat - text_.renderCentered(text, {.x = center_x, .y = center_y}, SCALE, SPACING); + const Vec2 CENTER = { + .x = scoreboard_zone.w / 2.0F, + .y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F), + }; + Systems::InitHud::drawScoreboardSegmentsAt(text_, buildScoreboardSegments(), CENTER, SCALE, SPACING); } -auto GameScene::buildScoreboard() const -> std::string { - // Puntuación P1 (6 dígits) - mostrar zeros si inactiu - std::string score_p1; - std::string vides_p1; +auto GameScene::buildScoreboardSegments() const -> Systems::InitHud::ScoreboardSegments { + Systems::InitHud::ScoreboardSegments out; + + // Puntuació P1 (6 dígits) - zeros si inactiu if (match_config_.player1_active) { - score_p1 = std::to_string(score_per_player_[0]); - score_p1 = std::string(6 - std::min(6, static_cast(score_p1.length())), '0') + score_p1; - vides_p1 = (lives_per_player_[0] < 10) + std::string s = std::to_string(score_per_player_[0]); + out.score_p1 = std::string(6 - std::min(6, static_cast(s.length())), '0') + s; + out.lives_p1 = (lives_per_player_[0] < 10) ? "0" + std::to_string(lives_per_player_[0]) : std::to_string(lives_per_player_[0]); } else { - score_p1 = "000000"; - vides_p1 = "00"; + out.score_p1 = "000000"; + out.lives_p1 = "00"; } - // Nivel (2 dígits) - uint8_t stage_num = stage_manager_->getCurrentStage(); - std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num) - : std::to_string(stage_num); + // Nivell (2 dígits) amb label localitzat + const uint8_t STAGE_NUM = stage_manager_->getCurrentStage(); + const std::string STAGE_STR = (STAGE_NUM < 10) ? "0" + std::to_string(STAGE_NUM) + : std::to_string(STAGE_NUM); + out.level = Locale::get().text("hud.level") + STAGE_STR; - // Puntuación P2 (6 dígits) - mostrar zeros si inactiu - std::string score_p2; - std::string vides_p2; + // Puntuació P2 (6 dígits) - zeros si inactiu if (match_config_.player2_active) { - score_p2 = std::to_string(score_per_player_[1]); - score_p2 = std::string(6 - std::min(6, static_cast(score_p2.length())), '0') + score_p2; - vides_p2 = (lives_per_player_[1] < 10) + std::string s = std::to_string(score_per_player_[1]); + out.score_p2 = std::string(6 - std::min(6, static_cast(s.length())), '0') + s; + out.lives_p2 = (lives_per_player_[1] < 10) ? "0" + std::to_string(lives_per_player_[1]) : std::to_string(lives_per_player_[1]); } else { - score_p2 = "000000"; - vides_p2 = "00"; + out.score_p2 = "000000"; + out.lives_p2 = "00"; } - - // Format: "123456 03 LEVEL 01 654321 02" - // Nota: dos espais entre seccions, mantenir ambdós slots siempre visibles - return score_p1 + " " + vides_p1 + " " + Locale::get().text("hud.level") + stage_str + " " + score_p2 + " " + vides_p2; + return out; } // [NEW] Stage system helper methods diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 4c9d413..a13f5a8 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -29,6 +29,7 @@ #include "game/stage_system/stage_config.hpp" #include "game/stage_system/stage_manager.hpp" #include "game/systems/collision_system.hpp" +#include "game/systems/init_hud_animator.hpp" // Game over state machine enum class GameOverState : uint8_t { @@ -128,8 +129,9 @@ class GameScene final : public Scene { void drawPlayingState(); void drawLevelCompletedState(); - // [NEW] Función helper del marcador - [[nodiscard]] auto buildScoreboard() const -> std::string; + // [NEW] Helper del marcador: construeix els 5 segments (score_p1, vides_p1, + // level, score_p2, vides_p2) per a render colorit per segment. + [[nodiscard]] auto buildScoreboardSegments() const -> Systems::InitHud::ScoreboardSegments; // Sub-pasos de update() (descompuestos en Fase 9d para reducir // complejidad cognitiva; cada uno es responsable de una sección). diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 9e2011c..b5f83e4 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -8,6 +8,7 @@ #include #include "core/defaults.hpp" +#include "core/defaults/hud.hpp" #include "core/math/easing.hpp" #include "core/rendering/line_renderer.hpp" @@ -77,8 +78,40 @@ namespace Systems::InitHud { } } + void drawScoreboardSegmentsAt(const Graphics::VectorText& text, + const ScoreboardSegments& segments, + const Vec2& center, + float scale, + float spacing) { + // Separadors entre segments (preservant el layout legacy: " ", " ", " ", " "). + const float W_SEP1 = Graphics::VectorText::getTextWidth(" ", scale, spacing); + const float W_SEP2 = Graphics::VectorText::getTextWidth(" ", scale, spacing); + + const float W_SP1 = Graphics::VectorText::getTextWidth(segments.score_p1, scale, spacing); + const float W_LP1 = Graphics::VectorText::getTextWidth(segments.lives_p1, scale, spacing); + const float W_LV = Graphics::VectorText::getTextWidth(segments.level, scale, spacing); + const float W_SP2 = Graphics::VectorText::getTextWidth(segments.score_p2, scale, spacing); + const float W_LP2 = Graphics::VectorText::getTextWidth(segments.lives_p2, scale, spacing); + + const float TOTAL = W_SP1 + W_SEP1 + W_LP1 + W_SEP2 + W_LV + W_SEP2 + W_SP2 + W_SEP1 + W_LP2; + + const float HEIGHT = Graphics::VectorText::getTextHeight(scale); + const float TOP_Y = center.y - (HEIGHT / 2.0F); + float x = center.x - (TOTAL / 2.0F); + + text.render(segments.score_p1, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::SCORE_P1); + x += W_SP1 + W_SEP1; + text.render(segments.lives_p1, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LIVES); + x += W_LP1 + W_SEP2; + text.render(segments.level, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL); + x += W_LV + W_SEP2; + text.render(segments.score_p2, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::SCORE_P2); + x += W_SP2 + W_SEP1; + text.render(segments.lives_p2, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LIVES); + } + void drawScoreboardAnimated(const Graphics::VectorText& text, - const std::string& scoreboard_text, + const ScoreboardSegments& segments, float progress) { const float EASED = Easing::easeOutQuad(progress); @@ -91,10 +124,7 @@ namespace Systems::InitHud { const auto Y_INI = static_cast(Defaults::Game::HEIGHT); const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED); - text.renderCentered(scoreboard_text, - Vec2{.x = CENTRE_X, .y = Y_ANIM}, - SCALE, - SPACING); + drawScoreboardSegmentsAt(text, segments, {.x = CENTRE_X, .y = Y_ANIM}, SCALE, SPACING); } } // namespace Systems::InitHud diff --git a/source/game/systems/init_hud_animator.hpp b/source/game/systems/init_hud_animator.hpp index 1775816..507f768 100644 --- a/source/game/systems/init_hud_animator.hpp +++ b/source/game/systems/init_hud_animator.hpp @@ -21,6 +21,17 @@ namespace Systems::InitHud { + // Segments del marcador. Cada segment es renderitza amb el seu propi color + // (vegeu Defaults::Hud::Colors). El layout final concatena en aquest ordre + // amb separadors d'1, 2, 2, 1 espais respectivament (igual que el legacy). + struct ScoreboardSegments { + std::string score_p1; + std::string lives_p1; + std::string level; // ex: "NIVELL 01" + std::string score_p2; + std::string lives_p2; + }; + // Convierte un progreso global 0..1 al sub-progreso de un elemento que solo // se anima en la ventana [ratio_init, ratio_end]. // < ratio_init → 0.0 (no empezó) @@ -40,10 +51,19 @@ namespace Systems::InitHud { // 66..100% → línea inferior crece desde los lados hacia el centro. void drawBordersAnimated(Rendering::Renderer* renderer, float progress); - // Dibuja el scoreboard centrado, subiendo desde fuera de la pantalla - // hasta su posición final con easing. + // Dibuixa els 5 segments del scoreboard centrats al voltant de `center`, + // cadascun amb el seu color (Defaults::Hud::Colors). Separadors de 1/2/2/1 + // espais entre segments per preservar el layout legacy. + void drawScoreboardSegmentsAt(const Graphics::VectorText& text, + const ScoreboardSegments& segments, + const Vec2& center, + float scale, + float spacing); + + // Dibuixa el scoreboard centrat, pujant des de fora de la pantalla fins a + // la seva posició final amb easing. Delega a drawScoreboardSegmentsAt. void drawScoreboardAnimated(const Graphics::VectorText& text, - const std::string& scoreboard_text, + const ScoreboardSegments& segments, float progress); } // namespace Systems::InitHud