From e4f8f586d60d41b41cef9d0f2ce8865231f160f6 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:06:01 +0200 Subject: [PATCH 01/18] tweak(hud): constants de l'esquema de color per jugador i de les icones de vides --- source/core/defaults/hud.hpp | 37 ++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index 4cc4fb3..ea55cbd 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -12,17 +12,46 @@ 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). Amb alpha=255 el line_renderer usa el color - // directament sense caure al fallback verd (Rendering::DEFAULT_LINE_COLOR). + // Esquema de color del marcador: "per jugador + sistema". Cada jugador usa + // el SEU color (parella brillant/atenuat) en tot el seu bloc (punts + vides); + // el nivell central va sempre en verd de sistema. Colors plans i purs: el + // glow/bloom el posa el shader de postpro, NO s'horneja al color. Amb + // alpha=255 el line_renderer usa el color directament sense caure al fallback + // verd (Rendering::DEFAULT_LINE_COLOR). namespace Colors { + // Jugador 1 → cian. + constexpr SDL_Color P1_BRIGHT = {.r = 41, .g = 231, .b = 255, .a = 255}; // #29E7FF + constexpr SDL_Color P1_DIM = {.r = 12, .g = 90, .b = 102, .a = 255}; // #0C5A66 + // Jugador 2 → groc. + constexpr SDL_Color P2_BRIGHT = {.r = 255, .g = 226, .b = 58, .a = 255}; // #FFE23A + constexpr SDL_Color P2_DIM = {.r = 110, .g = 98, .b = 16, .a = 255}; // #6E6210 + // 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 + // escalada, no un número). Mides derivades de l'alçada del marcador + // (Defaults::Zones::SCOREBOARD_BOTTOM_H) per encaixar-hi sempre. + namespace Lives { + constexpr float ICON_HEIGHT_RATIO = 0.45F; // alçada de la icona com a fracció de l'alçada del marcador + constexpr float ICON_SPACING_FACTOR = 1.35F; // separació centre-a-centre = alçada_icona × factor + constexpr int MAX_ICONS = 5; // límit d'icones dibuixades (acota l'ample del bloc) + } // namespace Lives + + // Disposició del marcador: bloc P1 ancorat a l'esquerra, bloc P2 a la dreta + // (mateix ordre intern "punts vides", no mirrored) i nivell centrat. Els + // blocs s'alineen amb les verticals del PLAYAREA. + namespace Layout { + constexpr float BLOCK_INNER_GAP = 24.0F; // separació punts↔vides dins d'un bloc (px lògics) + } // namespace Layout + // Animación de entrada del HUD (init_hud_animator). namespace InitAnim { // Spawn vertical de la nave: 50 px bajo la PLAYAREA (sale desde fuera). From 317e2a3fd94e4fcda75e31303bf5eb5038964ddf Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:09:28 +0200 Subject: [PATCH 02/18] 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 From a80822648189a2b1852837d139a50c143c9c5dcb Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:11:37 +0200 Subject: [PATCH 03/18] =?UTF-8?q?tweak(hud):=20zeros=20de=20farciment=20de?= =?UTF-8?q?=20la=20puntuaci=C3=B3=20atenuats=20i=20etiqueta=20NIVELL=20en?= =?UTF-8?q?=20verd=20atenuat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/game/systems/init_hud_animator.cpp | 47 ++++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 2fe99f2..7d6274b 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -86,13 +86,39 @@ namespace Systems::InitHud { 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). + // Pinta la puntuació amb els zeros de farciment previs al primer dígit + // significatiu en to atenuat i la resta en brillant (efecte display de 7 + // segments). Puntuació 0 (tot zeros) → tot atenuat. + void drawScore(const Graphics::VectorText& text, + const std::string& score, + SDL_Color bright, + SDL_Color dim, + float x, + float top_y, + float scale, + float spacing) { + const size_t SIG = score.find_first_not_of('0'); + const std::string PREFIX = (SIG == std::string::npos) ? score : score.substr(0, SIG); + const std::string REST = (SIG == std::string::npos) ? std::string{} : score.substr(SIG); + if (!PREFIX.empty()) { + text.render(PREFIX, {.x = x, .y = top_y}, scale, spacing, 1.0F, dim); + // Avança l'amplada del prefix més el buit inter-caràcter que hi + // hauria si fos un sol string (exacte per a qualsevol spacing). + x += Graphics::VectorText::getTextWidth(PREFIX, scale, spacing) + (spacing * scale); + } + if (!REST.empty()) { + text.render(REST, {.x = x, .y = top_y}, scale, spacing, 1.0F, bright); + } + } + + // Pinta el bloc d'un jugador "punts vides" amb el seu color (punts amb + // zeros atenuats, vides en brillant). Si right_align, el bloc acaba a + // anchor_x (ancorat a la dreta); si no, comença a anchor_x (esquerra). void drawPlayerBlock(const Graphics::VectorText& text, const std::string& score, int lives, - SDL_Color color, + SDL_Color bright, + SDL_Color dim, float anchor_x, float top_y, float scale, @@ -104,12 +130,13 @@ namespace Systems::InitHud { 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); + drawScore(text, score, bright, dim, x, top_y, scale, spacing); x += W_SCORE + Defaults::Hud::Layout::BLOCK_INNER_GAP; - text.render(LIVES_STR, {.x = x, .y = top_y}, scale, spacing, 1.0F, color); + text.render(LIVES_STR, {.x = x, .y = top_y}, scale, spacing, 1.0F, bright); } - // Pinta el nivell ("NIVELL" + número) centrat a la pantalla. + // Pinta el nivell centrat: etiqueta "NIVELL" en verd atenuat i el número + // en verd brillant. void drawLevel(const Graphics::VectorText& text, const std::string& label, const std::string& value, @@ -120,7 +147,7 @@ namespace Systems::InitHud { 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); + text.render(label, {.x = x, .y = top_y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL_DIM); x += W_LABEL; text.render(value, {.x = x, .y = top_y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL_BRIGHT); } @@ -138,8 +165,8 @@ namespace Systems::InitHud { const float RIGHT = play.x + play.w; const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); - 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); + drawPlayerBlock(text, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, LEFT, TOP_Y, scale, spacing, false); + drawPlayerBlock(text, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, RIGHT, TOP_Y, scale, spacing, true); drawLevel(text, data.level_label, data.level_value, TOP_Y, scale, spacing); } From 0abd661905755ca20fc9b374f8139fdb13b0aef4 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:14:32 +0200 Subject: [PATCH 04/18] =?UTF-8?q?tweak(hud):=20vides=20com=20a=20icones=20?= =?UTF-8?q?de=20la=20nau=20en=20miniatura=20en=20lloc=20d'un=20n=C3=BAmero?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/game/scenes/game_scene.cpp | 9 ++- source/game/systems/init_hud_animator.cpp | 80 ++++++++++++++++++----- source/game/systems/init_hud_animator.hpp | 14 +++- 3 files changed, 81 insertions(+), 22 deletions(-) diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 08caf57..bd09ba0 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_, buildScoreboardData(), score_progress); + Systems::InitHud::drawScoreboardAnimated(sdl_.getRenderer(), text_, buildScoreboardData(), score_progress); } if (ship1_progress > 0.0F && match_config_.player1_active && ships_[0].isActive()) { @@ -933,7 +933,7 @@ void GameScene::drawScoreboard() { text_.renderCentered(Locale::get().text("demo.banner"), CENTER, SCALE, SPACING); return; } - Systems::InitHud::drawScoreboardAt(text_, buildScoreboardData(), CENTER.y, SCALE, SPACING); + Systems::InitHud::drawScoreboardAt(sdl_.getRenderer(), text_, buildScoreboardData(), CENTER.y, SCALE, SPACING); } auto GameScene::buildScoreboardData() const -> Systems::InitHud::ScoreboardData { @@ -950,6 +950,11 @@ auto GameScene::buildScoreboardData() const -> Systems::InitHud::ScoreboardData 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; + // Shapes de les naus per a les icones de vides (reutilitza la geometria ja + // carregada de cada Ship). + out.shape_p1 = ships_[0].getShape(); + out.shape_p2 = ships_[1].getShape(); + // Nivell: etiqueta localitzada + número a 2 dígits (separats per pintar-los // amb tonalitats distintes). const uint8_t STAGE_NUM = stage_manager_->getCurrentStage(); diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 7d6274b..d46b865 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -11,6 +11,7 @@ #include "core/defaults/hud.hpp" #include "core/math/easing.hpp" #include "core/rendering/line_renderer.hpp" +#include "core/rendering/shape_renderer.hpp" namespace Systems::InitHud { @@ -80,10 +81,50 @@ namespace Systems::InitHud { 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; + // Alçada de la icona de vida (px lògics) derivada de l'alçada del marcador. + auto lifeIconHeight() -> float { + return Defaults::Hud::Lives::ICON_HEIGHT_RATIO * Defaults::Zones::SCOREBOARD_BOTTOM_H; + } + + // Nombre d'icones de vida a dibuixar (acotat a MAX_ICONS, mai negatiu). + auto lifeIconCount(int lives) -> int { + return std::clamp(lives, 0, Defaults::Hud::Lives::MAX_ICONS); + } + + // Ample del bloc de vides (0 si no hi ha vides). N icones = (N-1) passos + // de separació + l'amplada d'una icona. + auto livesBlockWidth(int lives) -> float { + const int N = lifeIconCount(lives); + if (N <= 0) { + return 0.0F; + } + const float ICON_H = lifeIconHeight(); + const float STEP = ICON_H * Defaults::Hud::Lives::ICON_SPACING_FACTOR; + return (static_cast(N - 1) * STEP) + ICON_H; + } + + // Dibuixa les vides com a icones de la nau (apuntant amunt, color del + // jugador). El glow el posa el shader. x_left = vora esquerra del bloc. + void drawLives(Rendering::Renderer* renderer, + const std::shared_ptr& shape, + int lives, + SDL_Color color, + float x_left, + float center_y) { + const int N = lifeIconCount(lives); + if (N <= 0 || !shape) { + return; + } + const float ICON_H = lifeIconHeight(); + const float STEP = ICON_H * Defaults::Hud::Lives::ICON_SPACING_FACTOR; + // Escala que ajusta el cercle circumscrit de la shape a l'alçada + // objectiu (mida predictible independent del .shp). + const float RADIUS = shape->getBoundingRadius(); + const float SCALE = (RADIUS > 0.001F) ? (ICON_H / (2.0F * RADIUS)) : 1.0F; + for (int i = 0; i < N; i++) { + const Vec2 POS = {.x = x_left + (ICON_H / 2.0F) + (static_cast(i) * STEP), .y = center_y}; + Rendering::renderShape(renderer, shape, POS, 0.0F, SCALE, 1.0F, 1.0F, color); + } } // Pinta la puntuació amb els zeros de farciment previs al primer dígit @@ -112,27 +153,30 @@ namespace Systems::InitHud { } // Pinta el bloc d'un jugador "punts vides" amb el seu color (punts amb - // zeros atenuats, vides en brillant). Si right_align, el bloc acaba a - // anchor_x (ancorat a la dreta); si no, comença a anchor_x (esquerra). - void drawPlayerBlock(const Graphics::VectorText& text, + // zeros atenuats, vides com a icones de nau en brillant). Si right_align, + // el bloc acaba a anchor_x (ancorat a la dreta); si no, comença a + // anchor_x (esquerra). + void drawPlayerBlock(Rendering::Renderer* renderer, + const Graphics::VectorText& text, + const std::shared_ptr& shape, const std::string& score, int lives, SDL_Color bright, SDL_Color dim, float anchor_x, - float top_y, + float center_y, float scale, float spacing, bool right_align) { - const std::string LIVES_STR = livesText(lives); + const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing); - const float W_LIVES = Graphics::VectorText::getTextWidth(LIVES_STR, scale, spacing); + const float W_LIVES = livesBlockWidth(lives); const float BLOCK_W = W_SCORE + Defaults::Hud::Layout::BLOCK_INNER_GAP + W_LIVES; float x = right_align ? (anchor_x - BLOCK_W) : anchor_x; - drawScore(text, score, bright, dim, x, top_y, scale, spacing); + drawScore(text, score, bright, dim, x, TOP_Y, scale, spacing); x += W_SCORE + Defaults::Hud::Layout::BLOCK_INNER_GAP; - text.render(LIVES_STR, {.x = x, .y = top_y}, scale, spacing, 1.0F, bright); + drawLives(renderer, shape, lives, bright, x, center_y); } // Pinta el nivell centrat: etiqueta "NIVELL" en verd atenuat i el número @@ -154,7 +198,8 @@ namespace Systems::InitHud { } // namespace - void drawScoreboardAt(const Graphics::VectorText& text, + void drawScoreboardAt(Rendering::Renderer* renderer, + const Graphics::VectorText& text, const ScoreboardData& data, float center_y, float scale, @@ -165,12 +210,13 @@ namespace Systems::InitHud { const float RIGHT = play.x + play.w; const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); - drawPlayerBlock(text, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, LEFT, TOP_Y, scale, spacing, false); - drawPlayerBlock(text, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, RIGHT, TOP_Y, scale, spacing, true); + drawPlayerBlock(renderer, text, data.shape_p1, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, LEFT, center_y, scale, spacing, false); + drawPlayerBlock(renderer, text, data.shape_p2, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, RIGHT, center_y, scale, spacing, true); drawLevel(text, data.level_label, data.level_value, TOP_Y, scale, spacing); } - void drawScoreboardAnimated(const Graphics::VectorText& text, + void drawScoreboardAnimated(Rendering::Renderer* renderer, + const Graphics::VectorText& text, const ScoreboardData& data, float progress) { const float EASED = Easing::easeOutQuad(progress); @@ -183,7 +229,7 @@ namespace Systems::InitHud { const auto Y_INI = static_cast(Defaults::Game::HEIGHT); const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED); - drawScoreboardAt(text, data, Y_ANIM, SCALE, SPACING); + drawScoreboardAt(renderer, 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 eb9413e..84cb337 100644 --- a/source/game/systems/init_hud_animator.hpp +++ b/source/game/systems/init_hud_animator.hpp @@ -13,8 +13,10 @@ #pragma once +#include #include +#include "core/graphics/shape.hpp" #include "core/graphics/vector_text.hpp" #include "core/rendering/render_context.hpp" #include "core/types.hpp" @@ -32,6 +34,9 @@ namespace Systems::InitHud { int lives_p2{0}; // vides P2 std::string level_label; // ex: "NIVELL " std::string level_value; // ex: "01" + // Shapes de les naus per dibuixar les vides com a icones en miniatura. + std::shared_ptr shape_p1; + std::shared_ptr shape_p2; }; // Convierte un progreso global 0..1 al sub-progreso de un elemento que solo @@ -55,8 +60,10 @@ namespace Systems::InitHud { // 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, + // el seu color (Defaults::Hud::Colors). El renderer cal per dibuixar les + // icones de vides (shapes de nau). + void drawScoreboardAt(Rendering::Renderer* renderer, + const Graphics::VectorText& text, const ScoreboardData& data, float center_y, float scale, @@ -64,7 +71,8 @@ namespace Systems::InitHud { // 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, + void drawScoreboardAnimated(Rendering::Renderer* renderer, + const Graphics::VectorText& text, const ScoreboardData& data, float progress); From a7233e13dfcff9105a6243113f42444aebd8e6bf Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:40:40 +0200 Subject: [PATCH 05/18] =?UTF-8?q?tweak(hud):=20MAX=5FVIDES=20com=20a=20fon?= =?UTF-8?q?t=20=C3=BAnica=20de=20vides=20i=20recalibra=20el=20groc=20atenu?= =?UTF-8?q?at=20de=20P2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults/game.hpp | 3 ++- source/core/defaults/hud.hpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/core/defaults/game.hpp b/source/core/defaults/game.hpp index 76463dd..60ce828 100644 --- a/source/core/defaults/game.hpp +++ b/source/core/defaults/game.hpp @@ -10,7 +10,8 @@ namespace Defaults::Game { constexpr int HEIGHT = 720; // Regles de partida - constexpr int STARTING_LIVES = 3; // Initial lives + constexpr int MAX_VIDES = 3; // Vides màximes per jugador (font única; el HUD en deriva els slots) + constexpr int STARTING_LIVES = MAX_VIDES; // S'arrenca amb les vides al màxim constexpr float DEATH_DURATION = 3.0F; // Seconds of death animation constexpr float GAME_OVER_DURATION = 5.0F; // Seconds to display game over diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index 60d6b8a..da6f0e5 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -24,7 +24,7 @@ namespace Defaults::Hud { constexpr SDL_Color P1_DIM = {.r = 12, .g = 90, .b = 102, .a = 255}; // #0C5A66 // Jugador 2 → groc. constexpr SDL_Color P2_BRIGHT = {.r = 255, .g = 226, .b = 58, .a = 255}; // #FFE23A - constexpr SDL_Color P2_DIM = {.r = 110, .g = 98, .b = 16, .a = 255}; // #6E6210 + constexpr SDL_Color P2_DIM = {.r = 90, .g = 82, .b = 16, .a = 255}; // #5A5210 // 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 From 3bc87ad6525650e95b9bb5db9ba7785434c7dece Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:41:05 +0200 Subject: [PATCH 06/18] =?UTF-8?q?tweak(hud):=20l'=C3=BAltim=20d=C3=ADgit?= =?UTF-8?q?=20de=20la=20puntuaci=C3=B3=20sempre=20enc=C3=A8s=20(puntuaci?= =?UTF-8?q?=C3=B3=200=20no=20apaga=20el=20marcador)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/game/systems/init_hud_animator.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index d46b865..385a075 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -129,7 +129,8 @@ namespace Systems::InitHud { // Pinta la puntuació amb els zeros de farciment previs al primer dígit // significatiu en to atenuat i la resta en brillant (efecte display de 7 - // segments). Puntuació 0 (tot zeros) → tot atenuat. + // segments). El dígit menys significatiu SEMPRE va encès: puntuació 0 → + // cinc zeros atenuats + l'últim "0" encès (el marcador no queda mai apagat). void drawScore(const Graphics::VectorText& text, const std::string& score, SDL_Color bright, @@ -138,9 +139,14 @@ namespace Systems::InitHud { float top_y, float scale, float spacing) { - const size_t SIG = score.find_first_not_of('0'); - const std::string PREFIX = (SIG == std::string::npos) ? score : score.substr(0, SIG); - const std::string REST = (SIG == std::string::npos) ? std::string{} : score.substr(SIG); + if (score.empty()) { + return; + } + // Primer dígit significatiu; si són tots zeros, força l'últim a encès. + const size_t FIRST_SIG = score.find_first_not_of('0'); + const size_t SIG = (FIRST_SIG == std::string::npos) ? (score.size() - 1) : FIRST_SIG; + const std::string PREFIX = score.substr(0, SIG); + const std::string REST = score.substr(SIG); if (!PREFIX.empty()) { text.render(PREFIX, {.x = x, .y = top_y}, scale, spacing, 1.0F, dim); // Avança l'amplada del prefix més el buit inter-caràcter que hi From 462e91d967dcefbe772952c69e25a26525f3230f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:41:32 +0200 Subject: [PATCH 07/18] tweak(hud): restaura el tracking de les xifres del marcador (spacing 2.0) --- source/core/defaults/hud.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index da6f0e5..5e329b9 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -10,7 +10,7 @@ namespace Defaults::Hud { // Marcador (scoreboard inferior). Usado por GameScene::drawScoreboard() // y por la animación de entrada en init_hud_animator. constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; - constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; + constexpr float SCOREBOARD_TEXT_SPACING = 2.0F; // tracking de les xifres (per defecte del text del joc) // Esquema de color del marcador: "per jugador + sistema". Cada jugador usa // el SEU color (parella brillant/atenuat) en tot el seu bloc (punts + vides); From 17e9206d2664115210e5e87138c5a28e1a620086 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:43:26 +0200 Subject: [PATCH 08/18] tweak(hud): vides com a slots fixos (NUM_SLOTS = MAX_VIDES-1) que s'encenen/atenuen --- source/core/defaults/hud.hpp | 18 +----- source/game/systems/init_hud_animator.cpp | 75 +++++++++++++---------- 2 files changed, 45 insertions(+), 48 deletions(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index 5e329b9..c734e49 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -30,21 +30,9 @@ namespace Defaults::Hud { constexpr SDL_Color LEVEL_DIM = {.r = 29, .g = 107, .b = 44, .a = 255}; // #1D6B2C } // namespace Colors - // Vides representades com a icones de la nau (reutilitza la shape de la nau - // escalada, no un número). Mides derivades de l'alçada del marcador - // (Defaults::Zones::SCOREBOARD_BOTTOM_H) per encaixar-hi sempre. - namespace Lives { - constexpr float ICON_HEIGHT_RATIO = 0.45F; // alçada de la icona com a fracció de l'alçada del marcador - constexpr float ICON_SPACING_FACTOR = 1.35F; // separació centre-a-centre = alçada_icona × factor - constexpr int MAX_ICONS = 5; // límit d'icones dibuixades (acota l'ample del bloc) - } // namespace Lives - - // Disposició del marcador: bloc P1 ancorat a l'esquerra, bloc P2 a la dreta - // (mateix ordre intern "punts vides", no mirrored) i nivell centrat. Els - // blocs s'alineen amb les verticals del PLAYAREA. - namespace Layout { - constexpr float BLOCK_INNER_GAP = 24.0F; // separació punts↔vides dins d'un bloc (px lògics) - } // namespace Layout + // Les vides es dibuixen com a slots fixos de naus en miniatura (NUM_SLOTS = + // MAX_VIDES − 1). Mida i pas dels slots es deriven de la mètrica del glif del + // dígit a init_hud_animator, no de constants soltes. // Animación de entrada del HUD (init_hud_animator). namespace InitAnim { diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 385a075..c3cb25d 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -81,49 +81,57 @@ namespace Systems::InitHud { namespace { - // Alçada de la icona de vida (px lògics) derivada de l'alçada del marcador. - auto lifeIconHeight() -> float { - return Defaults::Hud::Lives::ICON_HEIGHT_RATIO * Defaults::Zones::SCOREBOARD_BOTTOM_H; + // Nombre de slots de vides: una nau menys que el màxim (la nau en joc + // no es dibuixa; els slots són repuestos). Deriva de MAX_VIDES. + constexpr int NUM_SLOTS = Defaults::Game::MAX_VIDES - 1; + + // Pas d'un dígit (amplada + tracking, escalat): és la diferència entre + // l'ample de dos caràcters i el d'un. Marca el ritme de tot el bloc. + auto digitPitch(float scale, float spacing) -> float { + return Graphics::VectorText::getTextWidth("00", scale, spacing) - + Graphics::VectorText::getTextWidth("0", scale, spacing); } - // Nombre d'icones de vida a dibuixar (acotat a MAX_ICONS, mai negatiu). - auto lifeIconCount(int lives) -> int { - return std::clamp(lives, 0, Defaults::Hud::Lives::MAX_ICONS); + // Mida d'un slot = alçada de la majúscula del dígit (mètrica del glif). + auto slotSize(float scale) -> float { + return Graphics::VectorText::getTextHeight(scale); } - // Ample del bloc de vides (0 si no hi ha vides). N icones = (N-1) passos - // de separació + l'amplada d'una icona. - auto livesBlockWidth(int lives) -> float { - const int N = lifeIconCount(lives); - if (N <= 0) { + // Ample del bloc de slots: constant, independent de les vides. NUM_SLOTS + // slots al pas del dígit (l'últim slot ocupa la seva pròpia mida). + auto slotsBlockWidth(float scale, float spacing) -> float { + if (NUM_SLOTS <= 0) { return 0.0F; } - const float ICON_H = lifeIconHeight(); - const float STEP = ICON_H * Defaults::Hud::Lives::ICON_SPACING_FACTOR; - return (static_cast(N - 1) * STEP) + ICON_H; + return (static_cast(NUM_SLOTS - 1) * digitPitch(scale, spacing)) + slotSize(scale); } - // Dibuixa les vides com a icones de la nau (apuntant amunt, color del - // jugador). El glow el posa el shader. x_left = vora esquerra del bloc. - void drawLives(Rendering::Renderer* renderer, + // Dibuixa els slots de vides com a naus en miniatura en posicions FIXES. + // Slot amb vida disponible (repuesto) → color encès; slot buit → atenuat. + // Repuestos = vides − 1 (la nau en joc no compta com a slot). + void drawSlots(Rendering::Renderer* renderer, const std::shared_ptr& shape, int lives, - SDL_Color color, + SDL_Color bright, + SDL_Color dim, float x_left, - float center_y) { - const int N = lifeIconCount(lives); - if (N <= 0 || !shape) { + float center_y, + float scale, + float spacing) { + if (NUM_SLOTS <= 0 || !shape) { return; } - const float ICON_H = lifeIconHeight(); - const float STEP = ICON_H * Defaults::Hud::Lives::ICON_SPACING_FACTOR; - // Escala que ajusta el cercle circumscrit de la shape a l'alçada - // objectiu (mida predictible independent del .shp). + const float SIZE = slotSize(scale); + const float PITCH = digitPitch(scale, spacing); + // Escala que ajusta el cercle circumscrit de la shape a la mida del + // slot (mida predictible independent del .shp). const float RADIUS = shape->getBoundingRadius(); - const float SCALE = (RADIUS > 0.001F) ? (ICON_H / (2.0F * RADIUS)) : 1.0F; - for (int i = 0; i < N; i++) { - const Vec2 POS = {.x = x_left + (ICON_H / 2.0F) + (static_cast(i) * STEP), .y = center_y}; - Rendering::renderShape(renderer, shape, POS, 0.0F, SCALE, 1.0F, 1.0F, color); + const float ICON_SCALE = (RADIUS > 0.001F) ? (SIZE / (2.0F * RADIUS)) : 1.0F; + const int FILLED = std::clamp(lives - 1, 0, NUM_SLOTS); + for (int i = 0; i < NUM_SLOTS; i++) { + const SDL_Color COLOR = (i < FILLED) ? bright : dim; + const Vec2 POS = {.x = x_left + (SIZE / 2.0F) + (static_cast(i) * PITCH), .y = center_y}; + Rendering::renderShape(renderer, shape, POS, 0.0F, ICON_SCALE, 1.0F, 1.0F, COLOR); } } @@ -176,13 +184,14 @@ namespace Systems::InitHud { bool right_align) { const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing); - const float W_LIVES = livesBlockWidth(lives); - const float BLOCK_W = W_SCORE + Defaults::Hud::Layout::BLOCK_INNER_GAP + W_LIVES; + const float GAP = digitPitch(scale, spacing); // separació punts↔slots = un pas de dígit + const float W_LIVES = slotsBlockWidth(scale, spacing); + const float BLOCK_W = W_SCORE + GAP + W_LIVES; float x = right_align ? (anchor_x - BLOCK_W) : anchor_x; drawScore(text, score, bright, dim, x, TOP_Y, scale, spacing); - x += W_SCORE + Defaults::Hud::Layout::BLOCK_INNER_GAP; - drawLives(renderer, shape, lives, bright, x, center_y); + x += W_SCORE + GAP; + drawSlots(renderer, shape, lives, bright, dim, x, center_y, scale, spacing); } // Pinta el nivell centrat: etiqueta "NIVELL" en verd atenuat i el número From 56065995fde0c881a09b0cea17c961ad4ed8cd69 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:44:37 +0200 Subject: [PATCH 09/18] tweak(hud): fila del marcador centrada amb posicions fixes (sense justificar a les vores) --- source/game/systems/init_hud_animator.cpp | 50 +++++++++++++++-------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index c3cb25d..80f886f 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -166,10 +166,21 @@ namespace Systems::InitHud { } } + // Separació punts↔slots dins d'un bloc = un pas de dígit (ritme únic). + auto blockInnerGap(float scale, float spacing) -> float { + return digitPitch(scale, spacing); + } + + // Ample (constant) del bloc d'un jugador: 6 dígits + separació + slots. + // No depèn de les vides, així res es recol·loca quan se'n perden. + auto playerBlockWidth(float scale, float spacing) -> float { + return Graphics::VectorText::getTextWidth("000000", scale, spacing) + + blockInnerGap(scale, spacing) + slotsBlockWidth(scale, spacing); + } + // Pinta el bloc d'un jugador "punts vides" amb el seu color (punts amb - // zeros atenuats, vides com a icones de nau en brillant). Si right_align, - // el bloc acaba a anchor_x (ancorat a la dreta); si no, comença a - // anchor_x (esquerra). + // zeros atenuats, vides com a slots de nau). Ancorat a x_left (vora + // esquerra del bloc), mateix ordre per a P1 i P2 (no mirrored). void drawPlayerBlock(Rendering::Renderer* renderer, const Graphics::VectorText& text, const std::shared_ptr& shape, @@ -177,20 +188,16 @@ namespace Systems::InitHud { int lives, SDL_Color bright, SDL_Color dim, - float anchor_x, + float x_left, float center_y, float scale, - float spacing, - bool right_align) { + float spacing) { const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing); - const float GAP = digitPitch(scale, spacing); // separació punts↔slots = un pas de dígit - const float W_LIVES = slotsBlockWidth(scale, spacing); - const float BLOCK_W = W_SCORE + GAP + W_LIVES; - float x = right_align ? (anchor_x - BLOCK_W) : anchor_x; + float x = x_left; drawScore(text, score, bright, dim, x, TOP_Y, scale, spacing); - x += W_SCORE + GAP; + x += W_SCORE + blockInnerGap(scale, spacing); drawSlots(renderer, shape, lives, bright, dim, x, center_y, scale, spacing); } @@ -219,15 +226,24 @@ namespace Systems::InitHud { float center_y, float scale, float 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; + // Fila centrada amb posicions FIXES: [bloc P1] · [NIVELL] · [bloc P2]. + // Els blocs tenen ample constant (slots fixos), així NIVELL queda centrat + // i res es recol·loca en perdre vides. Separadors derivats del glif + // (dos espais), com el disseny original. const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); + const float BLOCK_W = playerBlockWidth(scale, spacing); + const float W_LEVEL = Graphics::VectorText::getTextWidth(data.level_label, scale, spacing) + + Graphics::VectorText::getTextWidth(data.level_value, scale, spacing); + const float GAP = Graphics::VectorText::getTextWidth(" ", scale, spacing); + const float TOTAL = BLOCK_W + GAP + W_LEVEL + GAP + BLOCK_W; - drawPlayerBlock(renderer, text, data.shape_p1, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, LEFT, center_y, scale, spacing, false); - drawPlayerBlock(renderer, text, data.shape_p2, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, RIGHT, center_y, scale, spacing, true); + float x = (Defaults::Game::WIDTH / 2.0F) - (TOTAL / 2.0F); + drawPlayerBlock(renderer, text, data.shape_p1, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, x, center_y, scale, spacing); + x += BLOCK_W + GAP; + // NIVELL: drawLevel centra a WIDTH/2, que coincideix amb aquest tram. drawLevel(text, data.level_label, data.level_value, TOP_Y, scale, spacing); + x += W_LEVEL + GAP; + drawPlayerBlock(renderer, text, data.shape_p2, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, x, center_y, scale, spacing); } void drawScoreboardAnimated(Rendering::Renderer* renderer, From 0350063fb7570623a5764d2f3839f23e1669f505 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:52:17 +0200 Subject: [PATCH 10/18] tweak(hud): torna el tracking de les xifres a l'original (spacing 0.0) --- source/core/defaults/hud.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index c734e49..ee9d6b8 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -10,7 +10,7 @@ namespace Defaults::Hud { // Marcador (scoreboard inferior). Usado por GameScene::drawScoreboard() // y por la animación de entrada en init_hud_animator. constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; - constexpr float SCOREBOARD_TEXT_SPACING = 2.0F; // tracking de les xifres (per defecte del text del joc) + constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; // Esquema de color del marcador: "per jugador + sistema". Cada jugador usa // el SEU color (parella brillant/atenuat) en tot el seu bloc (punts + vides); From 9235e684e85a46622cc617dbf9927fe9c00fffce Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:52:17 +0200 Subject: [PATCH 11/18] =?UTF-8?q?tweak(hud):=20redueix=20els=20slots=20de?= =?UTF-8?q?=20vides=20a=20l'al=C3=A7ada=20real=20del=20glif=20i=20els=20pi?= =?UTF-8?q?nta=20sense=20glow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/graphics/vector_text.cpp | 9 +++++++-- source/core/graphics/vector_text.hpp | 4 ++++ source/game/systems/init_hud_animator.cpp | 8 +++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/source/core/graphics/vector_text.cpp b/source/core/graphics/vector_text.cpp index c916980..1f6f59f 100644 --- a/source/core/graphics/vector_text.cpp +++ b/source/core/graphics/vector_text.cpp @@ -11,8 +11,9 @@ namespace Graphics { // Constants para mides base dels caràcters - constexpr float BASE_CHAR_WIDTH = 20.0F; // Amplada base del caràcter - constexpr float BASE_CHAR_HEIGHT = 40.0F; // Altura base del caràcter + constexpr float BASE_CHAR_WIDTH = 20.0F; // Amplada base del caràcter (cel·la) + constexpr float BASE_CHAR_HEIGHT = 40.0F; // Altura base del caràcter (cel·la, amb marge) + constexpr float BASE_GLYPH_HEIGHT = 20.0F; // Altura real del glif (la majúscula/dígit ocupa 20 dels 40) VectorText::VectorText(Rendering::Renderer* renderer) : renderer_(renderer) { @@ -287,4 +288,8 @@ namespace Graphics { return BASE_CHAR_HEIGHT * scale; } + auto VectorText::getGlyphHeight(float scale) -> float { + return BASE_GLYPH_HEIGHT * scale; + } + } // namespace Graphics diff --git a/source/core/graphics/vector_text.hpp b/source/core/graphics/vector_text.hpp index 7db6dff..363023f 100644 --- a/source/core/graphics/vector_text.hpp +++ b/source/core/graphics/vector_text.hpp @@ -46,6 +46,10 @@ namespace Graphics { // Calcular altura del texto (útil para centrado vertical). [[nodiscard]] static auto getTextHeight(float scale = 1.0F) -> float; + // Altura real del glif (la majúscula/dígit, sense el marge vertical de la + // cel·la). Útil per dimensionar icones que han de casar amb el text. + [[nodiscard]] static auto getGlyphHeight(float scale = 1.0F) -> float; + // Verificar si un carácter está soportado [[nodiscard]] auto isSupported(char c) const -> bool; diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 80f886f..d4c7083 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -92,9 +92,10 @@ namespace Systems::InitHud { Graphics::VectorText::getTextWidth("0", scale, spacing); } - // Mida d'un slot = alçada de la majúscula del dígit (mètrica del glif). + // Mida d'un slot = alçada real del glif del dígit (no la cel·la, que té + // marge vertical: usar la cel·la feia les naus el doble de grans). auto slotSize(float scale) -> float { - return Graphics::VectorText::getTextHeight(scale); + return Graphics::VectorText::getGlyphHeight(scale); } // Ample del bloc de slots: constant, independent de les vides. NUM_SLOTS @@ -131,7 +132,8 @@ namespace Systems::InitHud { for (int i = 0; i < NUM_SLOTS; i++) { const SDL_Color COLOR = (i < FILLED) ? bright : dim; const Vec2 POS = {.x = x_left + (SIZE / 2.0F) + (static_cast(i) * PITCH), .y = center_y}; - Rendering::renderShape(renderer, shape, POS, 0.0F, ICON_SCALE, 1.0F, 1.0F, COLOR); + // glow=false: el marcador es manté net, com els dígits del text. + Rendering::renderShape(renderer, shape, POS, 0.0F, ICON_SCALE, 1.0F, 1.0F, COLOR, 0.0F, 1.0F, false); } } From 20825c8138333005a85915ad938cf49fe661b243 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 20:56:37 +0200 Subject: [PATCH 12/18] =?UTF-8?q?tweak(hud):=20puja=20una=20mica=20l'al?= =?UTF-8?q?=C3=A7ada=20dels=20slots=20de=20vides=20(factor=20d'ajust=20sob?= =?UTF-8?q?re=20el=20glif)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults/hud.hpp | 5 +++++ source/game/systems/init_hud_animator.cpp | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index ee9d6b8..a658910 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -12,6 +12,11 @@ namespace Defaults::Hud { constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; + // Ajust fi de l'alçada dels slots de vides respecte a l'alçada del glif del + // dígit: la silueta de la nau ompli menys que un dígit, així que un xicotet + // factor >1 la fa casar visualment amb les xifres (calibrat a ull). + constexpr float LIFE_SLOT_HEIGHT_FACTOR = 1.2F; + // Esquema de color del marcador: "per jugador + sistema". Cada jugador usa // el SEU color (parella brillant/atenuat) en tot el seu bloc (punts + vides); // el nivell central va sempre en verd de sistema. Colors plans i purs: el diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index d4c7083..b724c5d 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -93,9 +93,10 @@ namespace Systems::InitHud { } // Mida d'un slot = alçada real del glif del dígit (no la cel·la, que té - // marge vertical: usar la cel·la feia les naus el doble de grans). + // marge vertical: usar la cel·la feia les naus el doble de grans), amb un + // xicotet factor d'ajust perquè la silueta de la nau case amb les xifres. auto slotSize(float scale) -> float { - return Graphics::VectorText::getGlyphHeight(scale); + return Graphics::VectorText::getGlyphHeight(scale) * Defaults::Hud::LIFE_SLOT_HEIGHT_FACTOR; } // Ample del bloc de slots: constant, independent de les vides. NUM_SLOTS From 55b37ba59414665978cd14165c62c91c789cc383 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 21:00:21 +0200 Subject: [PATCH 13/18] =?UTF-8?q?tweak(hud):=20alinea=20verticalment=20els?= =?UTF-8?q?=20slots=20de=20vides=20amb=20la=20l=C3=ADnia=20del=20marcador?= =?UTF-8?q?=20(centre=20del=20bbox,=20no=20el=20declarat)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/game/systems/init_hud_animator.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index b724c5d..01a56f2 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "core/defaults.hpp" @@ -92,6 +93,22 @@ namespace Systems::InitHud { Graphics::VectorText::getTextWidth("0", scale, spacing); } + // Desplaçament vertical (unitats locals) entre el center declarat de la + // shape i el centre real del seu bbox. La nau té center (0,0) però el seu + // bbox no hi està centrat; cal per alinear el centre VISUAL de la nau amb + // la línia del marcador (els dígits sí tenen el center al mig del glif). + auto shapeVerticalOffset(const std::shared_ptr& shape) -> float { + float min_y = std::numeric_limits::max(); + float max_y = std::numeric_limits::lowest(); + for (const auto& prim : shape->getPrimitives()) { + for (const auto& point : prim.points) { + min_y = std::min(min_y, point.y); + max_y = std::max(max_y, point.y); + } + } + return ((min_y + max_y) / 2.0F) - shape->getCenter().y; + } + // Mida d'un slot = alçada real del glif del dígit (no la cel·la, que té // marge vertical: usar la cel·la feia les naus el doble de grans), amb un // xicotet factor d'ajust perquè la silueta de la nau case amb les xifres. @@ -129,10 +146,12 @@ namespace Systems::InitHud { // slot (mida predictible independent del .shp). const float RADIUS = shape->getBoundingRadius(); const float ICON_SCALE = (RADIUS > 0.001F) ? (SIZE / (2.0F * RADIUS)) : 1.0F; + // Alinea el centre visual de la nau amb la línia del marcador. + const float OFFSET_Y = shapeVerticalOffset(shape) * ICON_SCALE; const int FILLED = std::clamp(lives - 1, 0, NUM_SLOTS); for (int i = 0; i < NUM_SLOTS; i++) { const SDL_Color COLOR = (i < FILLED) ? bright : dim; - const Vec2 POS = {.x = x_left + (SIZE / 2.0F) + (static_cast(i) * PITCH), .y = center_y}; + const Vec2 POS = {.x = x_left + (SIZE / 2.0F) + (static_cast(i) * PITCH), .y = center_y - OFFSET_Y}; // glow=false: el marcador es manté net, com els dígits del text. Rendering::renderShape(renderer, shape, POS, 0.0F, ICON_SCALE, 1.0F, 1.0F, COLOR, 0.0F, 1.0F, false); } From 5ba562178ba7b3e2977c0fa1ce7a65aff89c770d Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 21:06:29 +0200 Subject: [PATCH 14/18] tweak(hud): el bloc d'un jugador inactiu es deixa apagat (sense dibuixar, reservant l'ample) --- source/game/scenes/game_scene.cpp | 2 ++ source/game/systems/init_hud_animator.cpp | 10 ++++++++-- source/game/systems/init_hud_animator.hpp | 10 ++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index bd09ba0..b07061f 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -945,6 +945,8 @@ auto GameScene::buildScoreboardData() const -> Systems::InitHud::ScoreboardData return std::string(6 - std::min(6, static_cast(S.length())), '0') + S; }; + out.p1_active = match_config_.player1_active; + out.p2_active = match_config_.player2_active; 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"; diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 01a56f2..3a9b8c7 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -208,12 +208,18 @@ namespace Systems::InitHud { const std::shared_ptr& shape, const std::string& score, int lives, + bool active, SDL_Color bright, SDL_Color dim, float x_left, float center_y, float scale, float spacing) { + // Jugador inactiu → bloc apagat: no es dibuixa res (es reserva l'ample + // igualment a drawScoreboardAt perquè NIVELL i el bloc actiu no es moguin). + if (!active) { + return; + } const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing); @@ -260,12 +266,12 @@ namespace Systems::InitHud { const float TOTAL = BLOCK_W + GAP + W_LEVEL + GAP + BLOCK_W; float x = (Defaults::Game::WIDTH / 2.0F) - (TOTAL / 2.0F); - drawPlayerBlock(renderer, text, data.shape_p1, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, x, center_y, scale, spacing); + drawPlayerBlock(renderer, text, data.shape_p1, data.score_p1, data.lives_p1, data.p1_active, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, x, center_y, scale, spacing); x += BLOCK_W + GAP; // NIVELL: drawLevel centra a WIDTH/2, que coincideix amb aquest tram. drawLevel(text, data.level_label, data.level_value, TOP_Y, scale, spacing); x += W_LEVEL + GAP; - drawPlayerBlock(renderer, text, data.shape_p2, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, x, center_y, scale, spacing); + drawPlayerBlock(renderer, text, data.shape_p2, data.score_p2, data.lives_p2, data.p2_active, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, x, center_y, scale, spacing); } void drawScoreboardAnimated(Rendering::Renderer* renderer, diff --git a/source/game/systems/init_hud_animator.hpp b/source/game/systems/init_hud_animator.hpp index 84cb337..4894ccc 100644 --- a/source/game/systems/init_hud_animator.hpp +++ b/source/game/systems/init_hud_animator.hpp @@ -28,10 +28,12 @@ namespace Systems::InitHud { // 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 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 + bool p1_active{false}; // jugador actiu? (inactiu → bloc apagat, sense dibuixar) + bool p2_active{false}; std::string level_label; // ex: "NIVELL " std::string level_value; // ex: "01" // Shapes de les naus per dibuixar les vides com a icones en miniatura. From 5b90a9a767863dac0e5d3e493db12c4f86bcb9b5 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 21:11:53 +0200 Subject: [PATCH 15/18] tweak(hud): jugador inactiu = marcador apagat (tot atenuat, no en blanc) --- source/game/systems/init_hud_animator.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 3a9b8c7..6c538e3 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -165,6 +165,7 @@ namespace Systems::InitHud { const std::string& score, SDL_Color bright, SDL_Color dim, + bool active, float x, float top_y, float scale, @@ -172,7 +173,10 @@ namespace Systems::InitHud { if (score.empty()) { return; } - // Primer dígit significatiu; si són tots zeros, força l'últim a encès. + // Jugador inactiu → marcador apagat: tots els dígits atenuats (no té + // "zero punts", simplement no en té). Jugador actiu → l'últim dígit + // sempre encès (puntuació 0 → cinc zeros atenuats + "0" encès). + const SDL_Color REST_COLOR = active ? bright : dim; const size_t FIRST_SIG = score.find_first_not_of('0'); const size_t SIG = (FIRST_SIG == std::string::npos) ? (score.size() - 1) : FIRST_SIG; const std::string PREFIX = score.substr(0, SIG); @@ -184,7 +188,7 @@ namespace Systems::InitHud { x += Graphics::VectorText::getTextWidth(PREFIX, scale, spacing) + (spacing * scale); } if (!REST.empty()) { - text.render(REST, {.x = x, .y = top_y}, scale, spacing, 1.0F, bright); + text.render(REST, {.x = x, .y = top_y}, scale, spacing, 1.0F, REST_COLOR); } } @@ -215,16 +219,13 @@ namespace Systems::InitHud { float center_y, float scale, float spacing) { - // Jugador inactiu → bloc apagat: no es dibuixa res (es reserva l'ample - // igualment a drawScoreboardAt perquè NIVELL i el bloc actiu no es moguin). - if (!active) { - return; - } + // Jugador inactiu → bloc apagat: es dibuixa igual però tot atenuat + // (punts i slots), com un display físic sense encendre. const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing); float x = x_left; - drawScore(text, score, bright, dim, x, TOP_Y, scale, spacing); + drawScore(text, score, bright, dim, active, x, TOP_Y, scale, spacing); x += W_SCORE + blockInnerGap(scale, spacing); drawSlots(renderer, shape, lives, bright, dim, x, center_y, scale, spacing); } From b412435862fbfc6936066b91b26976b0df18d72b Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 21:27:11 +0200 Subject: [PATCH 16/18] =?UTF-8?q?tweak(hud):=20NIVELL=20enc=C3=A8s=20i=20e?= =?UTF-8?q?l=20n=C3=BAmero=20amb=20els=20zeros=20de=20farciment=20atenuats?= =?UTF-8?q?=20com=20els=20punts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/game/systems/init_hud_animator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 6c538e3..4869c07 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -230,8 +230,8 @@ namespace Systems::InitHud { drawSlots(renderer, shape, lives, bright, dim, x, center_y, scale, spacing); } - // Pinta el nivell centrat: etiqueta "NIVELL" en verd atenuat i el número - // en verd brillant. + // Pinta el nivell centrat: etiqueta "NIVELL" encesa i el número com els + // punts (zeros de farciment atenuats, dígit significatiu en endavant encès). void drawLevel(const Graphics::VectorText& text, const std::string& label, const std::string& value, @@ -242,9 +242,9 @@ namespace Systems::InitHud { 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_DIM); + 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); + drawScore(text, value, Defaults::Hud::Colors::LEVEL_BRIGHT, Defaults::Hud::Colors::LEVEL_DIM, true, x, top_y, scale, spacing); } } // namespace From 8d18c50aaad0c53b03037b314fa82b0e51a9f84f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 21:36:51 +0200 Subject: [PATCH 17/18] =?UTF-8?q?tweak(hud):=20mode=20de=20vides=20commuta?= =?UTF-8?q?ble=20a=20Defaults=20(slots=20o=20d=C3=ADgits);=20per=20defecte?= =?UTF-8?q?=20d=C3=ADgits=20per=20veure'l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults/hud.hpp | 11 ++++++ source/game/systems/init_hud_animator.cpp | 48 ++++++++++++++++++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index a658910..78fbf71 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -5,6 +5,8 @@ #include +#include + namespace Defaults::Hud { // Marcador (scoreboard inferior). Usado por GameScene::drawScoreboard() @@ -12,6 +14,15 @@ namespace Defaults::Hud { constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; + // Mode de presentació de les vides al marcador (no es canvia en calent; + // es defineix ací mentre no estiga decidit si el nombre de vides serà fix). + // SLOTS → naus en miniatura en posicions fixes (s'encenen/atenuen). + // DIGITS → número de 2 dígits (mateixa regla que el nivell: zeros a + // l'esquerra atenuats, dígit significatiu en endavant encès). + enum class LivesDisplay : std::uint8_t { SLOTS, + DIGITS }; + constexpr LivesDisplay LIVES_DISPLAY = LivesDisplay::DIGITS; + // Ajust fi de l'alçada dels slots de vides respecte a l'alçada del glif del // dígit: la silueta de la nau ompli menys que un dígit, així que un xicotet // factor >1 la fa casar visualment amb les xifres (calibrat a ull). diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 4869c07..a1079a0 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -125,6 +125,20 @@ namespace Systems::InitHud { return (static_cast(NUM_SLOTS - 1) * digitPitch(scale, spacing)) + slotSize(scale); } + // Vides com a número de 2 dígits (zeros a l'esquerra). + auto livesDigits(int lives) -> std::string { + const std::string S = std::to_string(lives); + return (lives < 10) ? "0" + S : S; + } + + // Ample del bloc de vides segons el mode (constant en ambdós casos). + auto livesBlockWidth(float scale, float spacing) -> float { + if (Defaults::Hud::LIVES_DISPLAY == Defaults::Hud::LivesDisplay::DIGITS) { + return Graphics::VectorText::getTextWidth("00", scale, spacing); + } + return slotsBlockWidth(scale, spacing); + } + // Dibuixa els slots de vides com a naus en miniatura en posicions FIXES. // Slot amb vida disponible (repuesto) → color encès; slot buit → atenuat. // Repuestos = vides − 1 (la nau en joc no compta com a slot). @@ -197,16 +211,38 @@ namespace Systems::InitHud { return digitPitch(scale, spacing); } - // Ample (constant) del bloc d'un jugador: 6 dígits + separació + slots. + // Ample (constant) del bloc d'un jugador: 6 dígits + separació + vides. // No depèn de les vides, així res es recol·loca quan se'n perden. auto playerBlockWidth(float scale, float spacing) -> float { return Graphics::VectorText::getTextWidth("000000", scale, spacing) + - blockInnerGap(scale, spacing) + slotsBlockWidth(scale, spacing); + blockInnerGap(scale, spacing) + livesBlockWidth(scale, spacing); + } + + // Pinta el bloc de vides segons el mode: slots de nau o número de 2 dígits. + void drawLivesBlock(Rendering::Renderer* renderer, + const Graphics::VectorText& text, + const std::shared_ptr& shape, + int lives, + bool active, + SDL_Color bright, + SDL_Color dim, + float x_left, + float top_y, + float center_y, + float scale, + float spacing) { + if (Defaults::Hud::LIVES_DISPLAY == Defaults::Hud::LivesDisplay::DIGITS) { + // Mateixa regla que el nivell: zeros a l'esquerra atenuats. + drawScore(text, livesDigits(lives), bright, dim, active, x_left, top_y, scale, spacing); + return; + } + drawSlots(renderer, shape, lives, bright, dim, x_left, center_y, scale, spacing); } // Pinta el bloc d'un jugador "punts vides" amb el seu color (punts amb - // zeros atenuats, vides com a slots de nau). Ancorat a x_left (vora - // esquerra del bloc), mateix ordre per a P1 i P2 (no mirrored). + // zeros atenuats, vides com a slots de nau o número segons el mode). + // Ancorat a x_left (vora esquerra del bloc), mateix ordre per a P1 i P2 + // (no mirrored). void drawPlayerBlock(Rendering::Renderer* renderer, const Graphics::VectorText& text, const std::shared_ptr& shape, @@ -220,14 +256,14 @@ namespace Systems::InitHud { float scale, float spacing) { // Jugador inactiu → bloc apagat: es dibuixa igual però tot atenuat - // (punts i slots), com un display físic sense encendre. + // (punts i vides), com un display físic sense encendre. const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F); const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing); float x = x_left; drawScore(text, score, bright, dim, active, x, TOP_Y, scale, spacing); x += W_SCORE + blockInnerGap(scale, spacing); - drawSlots(renderer, shape, lives, bright, dim, x, center_y, scale, spacing); + drawLivesBlock(renderer, text, shape, lives, active, bright, dim, x, TOP_Y, center_y, scale, spacing); } // Pinta el nivell centrat: etiqueta "NIVELL" encesa i el número com els From a46b93c917d9a88208b1660023fc39c59e50956c Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 21:39:52 +0200 Subject: [PATCH 18/18] =?UTF-8?q?tweak(hud):=20el=20mode=20num=C3=A8ric=20?= =?UTF-8?q?de=20vides=20mostra=20repuestos=20(vides-1),=20coherent=20amb?= =?UTF-8?q?=20els=20slots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/game/systems/init_hud_animator.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index a1079a0..5c9a80a 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -232,8 +232,11 @@ namespace Systems::InitHud { float scale, float spacing) { if (Defaults::Hud::LIVES_DISPLAY == Defaults::Hud::LivesDisplay::DIGITS) { - // Mateixa regla que el nivell: zeros a l'esquerra atenuats. - drawScore(text, livesDigits(lives), bright, dim, active, x_left, top_y, scale, spacing); + // Repuestos = vides − 1 (la nau en joc no compta), igual que els + // slots. Mateixa regla de color que el nivell: zeros a l'esquerra + // atenuats, dígit significatiu en endavant encès. + const int SPARES = std::max(0, lives - 1); + drawScore(text, livesDigits(SPARES), bright, dim, active, x_left, top_y, scale, spacing); return; } drawSlots(renderer, shape, lives, bright, dim, x_left, center_y, scale, spacing);