tweak(hud): marcador en tres blocs ancorats (P1 esquerra, P2 dreta, nivell centrat) amb color per jugador

This commit is contained in:
2026-05-29 20:09:28 +02:00
parent e4f8f586d6
commit 317e2a3fd9
5 changed files with 104 additions and 88 deletions
-6
View File
@@ -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
+19 -31
View File
@@ -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<int>(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<int>(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<int>(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;
}
+3 -3
View File
@@ -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).
+62 -30
View File
@@ -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<float>(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
+20 -18
View File
@@ -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