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 4cc4fb3..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,17 +14,42 @@ 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). + // 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). + 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 + // 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 { - 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 + // 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 = 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 } // namespace Colors + // 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 { // Spawn vertical de la nave: 50 px bajo la PLAYAREA (sale desde fuera). 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/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 1790d10..b07061f 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(sdl_.getRenderer(), text_, buildScoreboardData(), score_progress); } if (ship1_progress > 0.0F && match_config_.player1_active && ships_[0].isActive()) { @@ -933,41 +933,36 @@ 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(sdl_.getRenderer(), 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.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"; + 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(); - 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..5c9a80a 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -5,12 +5,14 @@ #include #include +#include #include #include "core/defaults.hpp" #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 { @@ -78,53 +80,255 @@ namespace Systems::InitHud { } } - void drawScoreboardSegmentsAt(const Graphics::VectorText& text, - const ScoreboardSegments& segments, - const Vec2& center, + namespace { + + // 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); + } + + // 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. + auto slotSize(float scale) -> float { + return Graphics::VectorText::getGlyphHeight(scale) * Defaults::Hud::LIFE_SLOT_HEIGHT_FACTOR; + } + + // 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; + } + 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). + void drawSlots(Rendering::Renderer* renderer, + const std::shared_ptr& shape, + int lives, + SDL_Color bright, + SDL_Color dim, + float x_left, + float center_y, + float scale, + float spacing) { + if (NUM_SLOTS <= 0 || !shape) { + return; + } + 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 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 - 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); + } + } + + // 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). 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, + SDL_Color dim, + bool active, + float x, + float top_y, + float scale, + float spacing) { + if (score.empty()) { + return; + } + // 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); + 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 + // 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, REST_COLOR); + } + } + + // 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ó + 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) + 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) { + // 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); + } + + // Pinta el bloc d'un jugador "punts vides" amb el seu color (punts amb + // 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, + 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: es dibuixa igual però tot atenuat + // (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); + 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 + // 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, + 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; + drawScore(text, value, Defaults::Hud::Colors::LEVEL_BRIGHT, Defaults::Hud::Colors::LEVEL_DIM, true, x, top_y, scale, spacing); + } + + } // namespace + + void drawScoreboardAt(Rendering::Renderer* renderer, + 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); + // 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; - 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); + float x = (Defaults::Game::WIDTH / 2.0F) - (TOTAL / 2.0F); + 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, data.p2_active, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, x, center_y, scale, spacing); } - void drawScoreboardAnimated(const Graphics::VectorText& text, - const ScoreboardSegments& segments, + void drawScoreboardAnimated(Rendering::Renderer* renderer, + const Graphics::VectorText& text, + 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(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 507f768..4894ccc 100644 --- a/source/game/systems/init_hud_animator.hpp +++ b/source/game/systems/init_hud_animator.hpp @@ -13,23 +13,32 @@ #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" 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 + 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. + std::shared_ptr shape_p1; + std::shared_ptr shape_p2; }; // Convierte un progreso global 0..1 al sub-progreso de un elemento que solo @@ -51,19 +60,22 @@ 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). 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, 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 ScoreboardSegments& segments, + // Dibuixa el marcador pujant des de fora de la pantalla fins a la seva + // posició final amb easing. Delega a drawScoreboardAt. + void drawScoreboardAnimated(Rendering::Renderer* renderer, + const Graphics::VectorText& text, + const ScoreboardData& data, float progress); } // namespace Systems::InitHud