feat(hud): paleta per segments (P1 blanc, vides ambre, nivell verd, P2 rosa)

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