From 317e2a3fd94e4fcda75e31303bf5eb5038964ddf Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:09:28 +0200 Subject: [PATCH] tweak(hud): marcador en tres blocs ancorats (P1 esquerra, P2 dreta, nivell centrat) amb color per jugador --- source/core/defaults/hud.hpp | 6 -- source/game/scenes/game_scene.cpp | 50 +++++------- source/game/scenes/game_scene.hpp | 6 +- source/game/systems/init_hud_animator.cpp | 92 +++++++++++++++-------- source/game/systems/init_hud_animator.hpp | 38 +++++----- 5 files changed, 104 insertions(+), 88 deletions(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index ea55cbd..60d6b8a 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -28,12 +28,6 @@ namespace Defaults::Hud { // Nivell / sistema → verd. constexpr SDL_Color LEVEL_BRIGHT = {.r = 77, .g = 255, .b = 102, .a = 255}; // #4DFF66 constexpr SDL_Color LEVEL_DIM = {.r = 29, .g = 107, .b = 44, .a = 255}; // #1D6B2C - - // Colors legacy (es retiren en migrar el render del marcador). - 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 // Vides representades com a icones de la nau (reutilitza la shape de la nau diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 1790d10..08caf57 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -814,7 +814,7 @@ void GameScene::drawInitHudState() { } if (score_progress > 0.0F) { - Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboardSegments(), score_progress); + Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboardData(), score_progress); } if (ship1_progress > 0.0F && match_config_.player1_active && ships_[0].isActive()) { @@ -933,41 +933,29 @@ void GameScene::drawScoreboard() { text_.renderCentered(Locale::get().text("demo.banner"), CENTER, SCALE, SPACING); return; } - Systems::InitHud::drawScoreboardSegmentsAt(text_, buildScoreboardSegments(), CENTER, SCALE, SPACING); + Systems::InitHud::drawScoreboardAt(text_, buildScoreboardData(), CENTER.y, SCALE, SPACING); } -auto GameScene::buildScoreboardSegments() const -> Systems::InitHud::ScoreboardSegments { - Systems::InitHud::ScoreboardSegments out; +auto GameScene::buildScoreboardData() const -> Systems::InitHud::ScoreboardData { + Systems::InitHud::ScoreboardData out; - // Puntuació P1 (6 dígits) - zeros si inactiu - if (match_config_.player1_active) { - 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 { - out.score_p1 = "000000"; - out.lives_p1 = "00"; - } + // Puntuació a 6 dígits amb zeros a l'esquerra (inactiu → tot zeros, 0 vides). + const auto FORMAT_SCORE = [](int score) { + const std::string S = std::to_string(score); + return std::string(6 - std::min(6, static_cast(S.length())), '0') + S; + }; - // Nivell (2 dígits) amb label localitzat + out.score_p1 = match_config_.player1_active ? FORMAT_SCORE(score_per_player_[0]) : "000000"; + out.lives_p1 = match_config_.player1_active ? lives_per_player_[0] : 0; + out.score_p2 = match_config_.player2_active ? FORMAT_SCORE(score_per_player_[1]) : "000000"; + out.lives_p2 = match_config_.player2_active ? lives_per_player_[1] : 0; + + // Nivell: etiqueta localitzada + número a 2 dígits (separats per pintar-los + // amb tonalitats distintes). 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ó P2 (6 dígits) - zeros si inactiu - if (match_config_.player2_active) { - 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 { - out.score_p2 = "000000"; - out.lives_p2 = "00"; - } + out.level_label = Locale::get().text("hud.level"); + out.level_value = (STAGE_NUM < 10) ? "0" + std::to_string(STAGE_NUM) + : std::to_string(STAGE_NUM); return out; } diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 717b186..349a947 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -140,9 +140,9 @@ class GameScene final : public Scene { void drawPlayingState(); void drawLevelCompletedState(); - // [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; + // Helper del marcador: construeix les dades (puntuacions, vides i nivell) + // per al render en blocs ancorats per jugador. + [[nodiscard]] auto buildScoreboardData() const -> Systems::InitHud::ScoreboardData; // 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 b5f83e4..2fe99f2 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -78,53 +78,85 @@ namespace Systems::InitHud { } } - void drawScoreboardSegmentsAt(const Graphics::VectorText& text, - const ScoreboardSegments& segments, - const Vec2& center, + namespace { + + // Vides com a text (2 dígits) — provisional fins a migrar-les a icones. + auto livesText(int lives) -> std::string { + const std::string S = std::to_string(lives); + return (lives < 10) ? "0" + S : S; + } + + // Pinta el bloc d'un jugador "punts vides" amb el seu color. Si + // right_align, el bloc acaba a anchor_x (ancorat a la dreta); si no, + // comença a anchor_x (ancorat a l'esquerra). + void drawPlayerBlock(const Graphics::VectorText& text, + const std::string& score, + int lives, + SDL_Color color, + float anchor_x, + float top_y, + float scale, + float spacing, + bool right_align) { + const std::string LIVES_STR = livesText(lives); + const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing); + const float W_LIVES = Graphics::VectorText::getTextWidth(LIVES_STR, scale, spacing); + const float BLOCK_W = W_SCORE + Defaults::Hud::Layout::BLOCK_INNER_GAP + W_LIVES; + + float x = right_align ? (anchor_x - BLOCK_W) : anchor_x; + text.render(score, {.x = x, .y = top_y}, scale, spacing, 1.0F, color); + x += W_SCORE + Defaults::Hud::Layout::BLOCK_INNER_GAP; + text.render(LIVES_STR, {.x = x, .y = top_y}, scale, spacing, 1.0F, color); + } + + // Pinta el nivell ("NIVELL" + número) centrat a la pantalla. + void drawLevel(const Graphics::VectorText& text, + const std::string& label, + const std::string& value, + float top_y, + float scale, + float spacing) { + const float W_LABEL = Graphics::VectorText::getTextWidth(label, scale, spacing); + const float W_VALUE = Graphics::VectorText::getTextWidth(value, scale, spacing); + const float CX = Defaults::Game::WIDTH / 2.0F; + float x = CX - ((W_LABEL + W_VALUE) / 2.0F); + text.render(label, {.x = x, .y = top_y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL_BRIGHT); + x += W_LABEL; + text.render(value, {.x = x, .y = top_y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL_BRIGHT); + } + + } // namespace + + void drawScoreboardAt(const Graphics::VectorText& text, + const ScoreboardData& data, + float center_y, 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); + // Els blocs s'ancoren a les verticals del PLAYAREA (sota el marc). + const SDL_FRect& play = Defaults::Zones::PLAYAREA; + const float LEFT = play.x; + const float RIGHT = play.x + play.w; + const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); - 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); + drawPlayerBlock(text, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, LEFT, TOP_Y, scale, spacing, false); + drawPlayerBlock(text, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, RIGHT, TOP_Y, scale, spacing, true); + drawLevel(text, data.level_label, data.level_value, TOP_Y, scale, spacing); } void drawScoreboardAnimated(const Graphics::VectorText& text, - const ScoreboardSegments& segments, + const ScoreboardData& data, float progress) { const float EASED = Easing::easeOutQuad(progress); constexpr float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE; constexpr float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING; const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD; - const float CENTRE_X = scoreboard_zone.w / 2.0F; const float Y_FINAL = scoreboard_zone.y + (scoreboard_zone.h / 2.0F); // Posición inicial: fuera de la pantalla por debajo. const auto Y_INI = static_cast(Defaults::Game::HEIGHT); const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED); - drawScoreboardSegmentsAt(text, segments, {.x = CENTRE_X, .y = Y_ANIM}, SCALE, SPACING); + drawScoreboardAt(text, data, 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 507f768..eb9413e 100644 --- a/source/game/systems/init_hud_animator.hpp +++ b/source/game/systems/init_hud_animator.hpp @@ -21,15 +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; + // Dades del marcador. El render reparteix tres blocs ancorats: bloc P1 a + // l'esquerra i bloc P2 a la dreta (mateix ordre intern "punts vides", no + // mirrored), i el nivell centrat. Cada bloc de jugador es pinta amb el seu + // color (vegeu Defaults::Hud::Colors); el nivell, en verd de sistema. + struct ScoreboardData { + std::string score_p1; // 6 dígits, zeros a l'esquerra + std::string score_p2; // 6 dígits, zeros a l'esquerra + int lives_p1{0}; // vides P1 (icones de nau al render) + int lives_p2{0}; // vides P2 + std::string level_label; // ex: "NIVELL " + std::string level_value; // ex: "01" }; // Convierte un progreso global 0..1 al sub-progreso de un elemento que solo @@ -51,19 +53,19 @@ namespace Systems::InitHud { // 66..100% → línea inferior crece desde los lados hacia el centro. void drawBordersAnimated(Rendering::Renderer* renderer, float progress); - // 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, + // Dibuixa el marcador en tres blocs ancorats a la fila d'alçada `center_y`: + // bloc P1 a l'esquerra, bloc P2 a la dreta i nivell centrat. Cada bloc amb + // el seu color (Defaults::Hud::Colors). + void drawScoreboardAt(const Graphics::VectorText& text, + const ScoreboardData& data, + float center_y, 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. + // Dibuixa el marcador pujant des de fora de la pantalla fins a la seva + // posició final amb easing. Delega a drawScoreboardAt. void drawScoreboardAnimated(const Graphics::VectorText& text, - const ScoreboardSegments& segments, + const ScoreboardData& data, float progress); } // namespace Systems::InitHud