refactor: renombra jugador*/zona/radi/MARGE/origen/letra residuals a anglès

This commit is contained in:
2026-05-24 08:09:41 +02:00
parent d36ad7d1c5
commit 252e881e93
22 changed files with 265 additions and 265 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ namespace Defaults::FX::Glow {
// Neon glow per outline gruixut, aplicat automàticament per renderShape.
// Els gruixos d'halo són RÀTIOS del bounding_radius de la shape (escalat
// per scale), de manera que un pentàgon (radi 20) té halo gros i una bala
// (radi 3) té halo subtil. El core (últim pass) usa el gruix de línia
// per scale), de manera que un pentàgon (radius 20) té halo gros i una bala
// (radius 3) té halo subtil. El core (últim pass) usa el gruix de línia
// global (1.5px) — no escala amb la shape.
//
// Cap superior: si la shape és molt gran (logos del títol, intro), el
+1 -1
View File
@@ -59,7 +59,7 @@ namespace Defaults::Game {
constexpr float INIT_HUD_SHIP2_RATIO_INIT = 0.20F;
constexpr float INIT_HUD_SHIP2_RATIO_END = 1.0F;
// Posición inicial de la nave en INIT_HUD (75% de altura de zona de juego)
// Posición inicial de la nave en INIT_HUD (75% de altura de zone de juego)
constexpr float INIT_HUD_SHIP_START_Y_RATIO = 0.75F; // 75% desde el top de PLAYAREA
// Spawn positions (distribución horizontal para 2 jugadores)
+1 -1
View File
@@ -68,7 +68,7 @@ namespace Defaults::Title {
constexpr float FLOATING_SCALE = 1.0F * SHIP_BASE_SCALE; // Flotante: scale base
// Offset de entrada (ajustat automáticoament a l'scale)
// Fórmula: (radi màxim de la ship * scale de entrada) + margen
// Fórmula: (radius màxim de la ship * scale de entrada) + margen
constexpr float ENTRY_OFFSET = (SHIP_MAX_RADIUS * ENTRY_SCALE_START) + ENTRY_OFFSET_MARGIN;
// Vec2 de fuga (centro para l'animación de salida)
+10 -10
View File
@@ -23,12 +23,12 @@ namespace Graphics {
}
void Border::bumpAt(Vec2 contact_point, float strength) {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
const std::array<float, SIDE_COUNT> DISTANCES = {
/* TOP */ std::abs(contact_point.y - zona.y),
/* RIGHT */ std::abs((zona.x + zona.w) - contact_point.x),
/* BOTTOM */ std::abs((zona.y + zona.h) - contact_point.y),
/* LEFT */ std::abs(contact_point.x - zona.x)};
/* TOP */ std::abs(contact_point.y - zone.y),
/* RIGHT */ std::abs((zone.x + zone.w) - contact_point.x),
/* BOTTOM */ std::abs((zone.y + zone.h) - contact_point.y),
/* LEFT */ std::abs(contact_point.x - zone.x)};
int closest_idx = 0;
float closest_dist = DISTANCES[0];
@@ -71,11 +71,11 @@ namespace Graphics {
} // namespace
void Border::draw() const {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const int X1 = static_cast<int>(zona.x);
const int Y1 = static_cast<int>(zona.y);
const int X2 = static_cast<int>(zona.x + zona.w);
const int Y2 = static_cast<int>(zona.y + zona.h);
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
const int X1 = static_cast<int>(zone.x);
const int Y1 = static_cast<int>(zone.y);
const int X2 = static_cast<int>(zone.x + zone.w);
const int Y2 = static_cast<int>(zone.y + zone.h);
const int OFF_TOP = static_cast<int>(sides_[SIDE_TOP].displacement_px);
const int OFF_RIGHT = static_cast<int>(sides_[SIDE_RIGHT].displacement_px);
+9 -9
View File
@@ -141,9 +141,9 @@ namespace Graphics {
}
void Playfield::buildLines() {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const float CELL_W = zona.w / static_cast<float>(Defaults::Playfield::COLUMNS);
const float CELL_H = zona.h / static_cast<float>(Defaults::Playfield::ROWS);
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
const float CELL_W = zone.w / static_cast<float>(Defaults::Playfield::COLUMNS);
const float CELL_H = zone.h / static_cast<float>(Defaults::Playfield::ROWS);
const float SUB_W = CELL_W / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
const float SUB_H = CELL_H / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
const int SUB_VERTS = Defaults::Playfield::COLUMNS * Defaults::Playfield::SUBDIVISIONS;
@@ -154,14 +154,14 @@ namespace Graphics {
// Verticals: posicions i ∈ [1, SUB_VERTS-1].
for (int i = 1; i < SUB_VERTS; i++) {
const float X = zona.x + (static_cast<float>(i) * SUB_W);
const float X = zone.x + (static_cast<float>(i) * SUB_W);
const bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0;
const float BRIGHTNESS = IS_MAIN
? Defaults::Playfield::GRID_BRIGHTNESS
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
verticals.push_back(Line{
.start = {.x = X, .y = zona.y},
.end = {.x = X, .y = zona.y + zona.h},
.start = {.x = X, .y = zone.y},
.end = {.x = X, .y = zone.y + zone.h},
.brightness = BRIGHTNESS,
.spawn_time_s = 0.0F,
.is_vertical = true});
@@ -169,14 +169,14 @@ namespace Graphics {
// Horitzontals: posicions j ∈ [1, SUB_HORIZ-1].
for (int j = 1; j < SUB_HORIZ; j++) {
const float Y = zona.y + (static_cast<float>(j) * SUB_H);
const float Y = zone.y + (static_cast<float>(j) * SUB_H);
const bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0;
const float BRIGHTNESS = IS_MAIN
? Defaults::Playfield::GRID_BRIGHTNESS
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
horizontals.push_back(Line{
.start = {.x = zona.x, .y = Y},
.end = {.x = zona.x + zona.w, .y = Y},
.start = {.x = zone.x, .y = Y},
.end = {.x = zone.x + zone.w, .y = Y},
.brightness = BRIGHTNESS,
.spawn_time_s = 0.0F,
.is_vertical = false});
+12 -12
View File
@@ -25,11 +25,11 @@ namespace Graphics {
}
void StarfieldParallax::buildStars() {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const float MIN_X = zona.x;
const float MAX_X = zona.x + zona.w;
const float MIN_Y = zona.y;
const float MAX_Y = zona.y + zona.h;
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
const float MIN_X = zone.x;
const float MAX_X = zone.x + zone.w;
const float MIN_Y = zone.y;
const float MAX_Y = zone.y + zone.h;
// Color únic per a totes les estrelles: el mateix blanc-blau gel
// del starfield del títol (Defaults::Title::Colors::STARFIELD).
@@ -89,13 +89,13 @@ namespace Graphics {
}
void StarfieldParallax::update(float delta_time, Vec2 world_velocity) {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const float MIN_X = zona.x;
const float MAX_X = zona.x + zona.w;
const float MIN_Y = zona.y;
const float MAX_Y = zona.y + zona.h;
const float W = zona.w;
const float H = zona.h;
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
const float MIN_X = zone.x;
const float MAX_X = zone.x + zone.w;
const float MIN_Y = zone.y;
const float MAX_Y = zone.y + zone.h;
const float W = zone.w;
const float H = zone.h;
for (auto& star : stars_) {
const float FACTOR = layerParallax(star.layer);
+3 -3
View File
@@ -17,7 +17,7 @@ namespace Physics {
return false;
}
// Calcular radi combinat (con amplificador per hitbox generós)
// Calcular radius combinat (con amplificador per hitbox generós)
float suma_radis = (a.getCollisionRadius() + b.getCollisionRadius()) * amplifier;
float suma_radis_sq = suma_radis * suma_radis;
@@ -31,8 +31,8 @@ namespace Physics {
return dist_sq <= suma_radis_sq;
}
// Swept collision: una entitat mòbil (radi r_a) s'ha desplaçat de p0 a p1 aquest
// frame. Comprova si el segment expandit pel radi conjunt (r_a + radi de b, amb
// Swept collision: una entitat mòbil (radius r_a) s'ha desplaçat de p0 a p1 aquest
// frame. Comprova si el segment expandit pel radius conjunt (r_a + radius de b, amb
// amplificador) toca el cercle de l'entity b. Equival al check discrete quan
// p0 == p1 (sense moviment). Evita tunneling a velocitats altes.
inline auto checkCollisionSwept(const Vec2& p0, const Vec2& p1, float r_a, const Entities::Entity& b, float amplifier = 1.0F) -> bool {
+17 -17
View File
@@ -4,52 +4,52 @@
namespace GameConfig {
// Mode de juego
enum class Mode : std::uint8_t {
NORMAL, // Partida normal
DEMO // Mode demostració (futur)
};
// Mode de juego
enum class Mode : std::uint8_t {
NORMAL, // Partida normal
DEMO // Mode demostració (futur)
};
// Configuración de una match
struct MatchConfig {
bool jugador1_actiu{false}; // Es active el player 1?
bool jugador2_actiu{false}; // Es active el player 2?
// Configuración de una match
struct MatchConfig {
bool player1_active{false}; // Es active el player 1?
bool player2_active{false}; // Es active el player 2?
Mode mode{Mode::NORMAL}; // Mode de juego
// Métodos auxiliars
// Retorna true si solo hay un player active
[[nodiscard]] auto isSinglePlayer() const -> bool {
return (jugador1_actiu && !jugador2_actiu) ||
(!jugador1_actiu && jugador2_actiu);
return (player1_active && !player2_active) ||
(!player1_active && player2_active);
}
// Retorna true si hay dos jugadors active
[[nodiscard]] auto isCoop() const -> bool {
return jugador1_actiu && jugador2_actiu;
return player1_active && player2_active;
}
// Retorna true si no hay sin player active
[[nodiscard]] auto hasNoPlayers() const -> bool {
return !jugador1_actiu && !jugador2_actiu;
return !player1_active && !player2_active;
}
// Compte de jugadors active (0, 1 o 2)
[[nodiscard]] auto getPlayerCount() const -> uint8_t {
return (jugador1_actiu ? 1 : 0) + (jugador2_actiu ? 1 : 0);
return (player1_active ? 1 : 0) + (player2_active ? 1 : 0);
}
// Retorna l'ID de l'únic player active (0 o 1)
// Solo vàlid si es_un_jugador() retorna true
[[nodiscard]] auto getSinglePlayerId() const -> uint8_t {
if (jugador1_actiu && !jugador2_actiu) {
if (player1_active && !player2_active) {
return 0;
}
if (!jugador1_actiu && jugador2_actiu) {
if (!player1_active && player2_active) {
return 1;
}
return 0; // Fallback (necesario comprovar es_un_jugador() primer)
}
};
};
} // namespace GameConfig
+18 -18
View File
@@ -12,35 +12,35 @@ namespace Constants {
// Matemàtiques
constexpr float PI = Defaults::Math::PI;
// Helpers per comprovar límits de zona
// Helpers per comprovar límits de zone
inline auto isInPlayArea(float x, float y) -> bool {
const SDL_FPoint POINT = {x, y};
return SDL_PointInRectFloat(&POINT, &Defaults::Zones::PLAYAREA);
}
inline void getPlayAreaBounds(float& min_x, float& max_x, float& min_y, float& max_y) {
const auto& zona = Defaults::Zones::PLAYAREA;
min_x = zona.x;
max_x = zona.x + zona.w;
min_y = zona.y;
max_y = zona.y + zona.h;
const auto& zone = Defaults::Zones::PLAYAREA;
min_x = zone.x;
max_x = zone.x + zone.w;
min_y = zone.y;
max_y = zone.y + zone.h;
}
// Obtenir límits segurs (compensant radi de l'entidad)
inline void getSafePlayAreaBounds(float radi, float& min_x, float& max_x, float& min_y, float& max_y) {
const auto& zona = Defaults::Zones::PLAYAREA;
constexpr float MARGE_SEGURETAT = 10.0F; // Safety margin
// Obtenir límits segurs (compensant radius de l'entidad)
inline void getSafePlayAreaBounds(float radius, float& min_x, float& max_x, float& min_y, float& max_y) {
const auto& zone = Defaults::Zones::PLAYAREA;
constexpr float SAFETY_MARGIN = 10.0F; // Safety margin
min_x = zona.x + radi + MARGE_SEGURETAT;
max_x = zona.x + zona.w - radi - MARGE_SEGURETAT;
min_y = zona.y + radi + MARGE_SEGURETAT;
max_y = zona.y + zona.h - radi - MARGE_SEGURETAT;
min_x = zone.x + radius + SAFETY_MARGIN;
max_x = zone.x + zone.w - radius - SAFETY_MARGIN;
min_y = zone.y + radius + SAFETY_MARGIN;
max_y = zone.y + zone.h - radius - SAFETY_MARGIN;
}
// Obtenir centro de l'àrea de juego
inline void getPlayAreaCenter(float& centre_x, float& centre_y) {
const auto& zona = Defaults::Zones::PLAYAREA;
centre_x = zona.x + (zona.w / 2.0F);
centre_y = zona.y + (zona.h / 2.0F);
inline void getPlayAreaCenter(float& center_x, float& center_y) {
const auto& zone = Defaults::Zones::PLAYAREA;
center_x = zone.x + (zone.w / 2.0F);
center_y = zone.y + (zone.h / 2.0F);
}
} // namespace Constants
+3 -3
View File
@@ -61,7 +61,7 @@ namespace Effects {
}
}
void FireworkManager::spawn(const Vec2& origen,
void FireworkManager::spawn(const Vec2& origin,
SDL_Color color,
float initial_speed,
int n_points,
@@ -74,7 +74,7 @@ namespace Effects {
// Notificar als subscriptors (playfield pulses, etc.).
if (spawn_callback_) {
spawn_callback_(origen);
spawn_callback_(origin);
}
const float ANGLE_STEP = 2.0F * Defaults::Math::PI / static_cast<float>(n_points);
@@ -94,7 +94,7 @@ namespace Effects {
const float SPEED =
initial_speed + (randSigned() * Defaults::FX::Firework::SPEED_VARIATION);
fw->head = origen;
fw->head = origin;
fw->velocity = {.x = std::cos(ANGLE) * SPEED, .y = std::sin(ANGLE) * SPEED};
fw->acceleration = Defaults::FX::Firework::FRICTION;
+2 -2
View File
@@ -21,7 +21,7 @@ namespace Effects {
class FireworkManager {
public:
// Notificació opcional cada vegada que es genera un burst.
using SpawnCallback = std::function<void(Vec2 origen)>;
using SpawnCallback = std::function<void(Vec2 origin)>;
explicit FireworkManager(Rendering::Renderer* renderer);
@@ -37,7 +37,7 @@ namespace Effects {
// initial_brightness: 0..1.
// glow: si true, cada partícula es renderitza amb halo neon.
// glow_color: color del halo. Si alpha==0, agafa el color de la línia.
void spawn(const Vec2& origen,
void spawn(const Vec2& origin,
SDL_Color color = Defaults::FX::Firework::DEFAULT_COLOR,
float initial_speed = Defaults::FX::Firework::SPEED,
int n_points = Defaults::FX::Firework::N_POINTS,
+1 -1
View File
@@ -80,7 +80,7 @@ void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id) {
}
void Bullet::update(float /*delta_time*/) {
// No-op: la desactivació per fora-de-zona viu a
// No-op: la desactivació per fora-de-zone viu a
// Systems::Collision::desactivateOutOfBoundsBullets() perquè així té accés
// al DebrisManager i pot generar el "trencament" visual de la bala alhora.
// El moviment l'integra PhysicsWorld; postUpdate sincronitza center_ i prev_position_.
+1 -1
View File
@@ -131,7 +131,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
const int RANGE_Y = static_cast<int>(max_y - min_y);
center_.x = static_cast<float>((std::rand() % RANGE_X) + static_cast<int>(min_x));
center_.y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));
std::cout << "[Enemy] Advertencia: spawn sin zona segura tras "
std::cout << "[Enemy] Advertencia: spawn sin zone segura tras "
<< Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS << " intentos\n";
}
} else {
+4 -4
View File
@@ -44,10 +44,10 @@ void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
if (spawn_point != nullptr) {
center_ = *spawn_point;
} else {
float centre_x;
float centre_y;
Constants::getPlayAreaCenter(centre_x, centre_y);
center_ = {.x = centre_x, .y = centre_y};
float center_x;
float center_y;
Constants::getPlayAreaCenter(center_x, center_y);
center_ = {.x = center_x, .y = center_y};
}
// Reset orientación
+48 -48
View File
@@ -38,9 +38,9 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
// Debug output de la configuración
std::cout << "[GameScene] Configuración de match - P1: "
<< (match_config_.jugador1_actiu ? "ACTIU" : "INACTIU")
<< (match_config_.player1_active ? "ACTIU" : "INACTIU")
<< ", P2: "
<< (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU")
<< (match_config_.player2_active ? "ACTIU" : "INACTIU")
<< '\n';
// Consumir opciones (preparació per MODE_DEMO futur)
@@ -61,7 +61,7 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
// Basat en el codi Pascal original: line 376
std::srand(static_cast<unsigned>(std::time(nullptr)));
// Configurar el mundo físico con los límites de la zona de juego.
// Configurar el mundo físico con los límites de la zone de juego.
physics_world_.clear();
physics_world_.setBounds(Defaults::Zones::PLAYAREA);
@@ -77,17 +77,17 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
});
// Fireworks generen una ripple gran al playfield (ona d'aigua centrada al burst).
firework_manager_.setSpawnCallback([this](Vec2 origen) {
playfield_.notifyExplosion(origen);
firework_manager_.setSpawnCallback([this](Vec2 origin) {
playfield_.notifyExplosion(origin);
});
// Explosions properes a una paret també generen bump (falloff lineal amb la distància).
debris_manager_.setExplosionCallback([this](Vec2 center) {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const float DIST_LEFT = std::abs(center.x - zona.x);
const float DIST_RIGHT = std::abs((zona.x + zona.w) - center.x);
const float DIST_TOP = std::abs(center.y - zona.y);
const float DIST_BOTTOM = std::abs((zona.y + zona.h) - center.y);
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
const float DIST_LEFT = std::abs(center.x - zone.x);
const float DIST_RIGHT = std::abs((zone.x + zone.w) - center.x);
const float DIST_TOP = std::abs(center.y - zone.y);
const float DIST_BOTTOM = std::abs((zone.y + zone.h) - center.y);
const float MIN_DIST = std::min({DIST_LEFT, DIST_RIGHT, DIST_TOP, DIST_BOTTOM});
if (MIN_DIST > Defaults::Border::EXPLOSION_FALLOFF_PX) {
return;
@@ -130,9 +130,9 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
// Inicialitzar naves segons configuración (solo jugadors active)
for (uint8_t i = 0; i < 2; i++) {
bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
bool player_active = (i == 0) ? match_config_.player1_active : match_config_.player2_active;
if (jugador_actiu) {
if (player_active) {
// Jugador active: init normalment
Vec2 spawn_pos = getSpawnPoint(i);
ships_[i].init(&spawn_pos, false); // No invulnerability at start
@@ -248,11 +248,11 @@ void GameScene::stepPhysics(float delta_time) {
void GameScene::stepShootingInput() {
auto* input = Input::get();
if (match_config_.jugador1_actiu &&
if (match_config_.player1_active &&
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
fireBullet(0);
}
if (match_config_.jugador2_actiu &&
if (match_config_.player2_active &&
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
fireBullet(1);
}
@@ -267,16 +267,16 @@ void GameScene::stepMidGameJoin() {
// Solo se permite join si hay al menos un jugador vivo (no se puede
// hacer join en pantalla vacía).
const bool ALGU_VIU =
(match_config_.jugador1_actiu && hit_timer_per_player_[0] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER) ||
(match_config_.jugador2_actiu && hit_timer_per_player_[1] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER);
(match_config_.player1_active && hit_timer_per_player_[0] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER) ||
(match_config_.player2_active && hit_timer_per_player_[1] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER);
if (!ALGU_VIU) {
return;
}
auto* input = Input::get();
for (uint8_t pid = 0; pid < 2; pid++) {
const bool ACTIU = (pid == 0) ? match_config_.jugador1_actiu
: match_config_.jugador2_actiu;
const bool ACTIU = (pid == 0) ? match_config_.player1_active
: match_config_.player2_active;
const bool MUERTO_SIN_VIDAS = hit_timer_per_player_[pid] == Defaults::Game::HIT_TIMER_INACTIVE_PLAYER;
if (ACTIU && !MUERTO_SIN_VIDAS) {
continue; // jugador ya está jugando
@@ -375,8 +375,8 @@ void GameScene::stepDeathSequence(float delta_time) {
// Sin vidas: marcar definitivamente muerto y comprobar transición a CONTINUE.
hit_timer_per_player_[i] = Defaults::Game::HIT_TIMER_INACTIVE_PLAYER;
const bool P1_DEAD = !match_config_.jugador1_actiu || lives_per_player_[0] <= 0;
const bool P2_DEAD = !match_config_.jugador2_actiu || lives_per_player_[1] <= 0;
const bool P1_DEAD = !match_config_.player1_active || lives_per_player_[0] <= 0;
const bool P2_DEAD = !match_config_.player2_active || lives_per_player_[1] <= 0;
if (P1_DEAD && P2_DEAD) {
game_over_state_ = GameOverState::CONTINUE;
continue_counter_ = Defaults::Game::CONTINUE_COUNT_START;
@@ -440,10 +440,10 @@ void GameScene::runStageInitHud(float delta_time) {
Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
Defaults::Game::INIT_HUD_SHIP2_RATIO_END);
if (match_config_.jugador1_actiu && SHIP1_P < 1.0F) {
if (match_config_.player1_active && SHIP1_P < 1.0F) {
ships_[0].setCenter(Systems::InitHud::computeShipPosition(SHIP1_P, getSpawnPoint(0)));
}
if (match_config_.jugador2_actiu && SHIP2_P < 1.0F) {
if (match_config_.player2_active && SHIP2_P < 1.0F) {
ships_[1].setCenter(Systems::InitHud::computeShipPosition(SHIP2_P, getSpawnPoint(1)));
}
}
@@ -453,7 +453,7 @@ void GameScene::runStageLevelStart(float delta_time) {
// Ambas naves pueden moverse y disparar durante el intro.
for (uint8_t i = 0; i < 2; i++) {
const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
const bool ACTIU = (i == 0) ? match_config_.player1_active : match_config_.player2_active;
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
ships_[i].processInput(delta_time, i);
ships_[i].update(delta_time);
@@ -481,7 +481,7 @@ void GameScene::runStagePlaying(float delta_time) {
// Gameplay normal: ships activos + entidades + colisiones + efectos.
for (uint8_t i = 0; i < 2; i++) {
const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
const bool ACTIU = (i == 0) ? match_config_.player1_active : match_config_.player2_active;
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
ships_[i].processInput(delta_time, i);
ships_[i].update(delta_time);
@@ -491,7 +491,7 @@ void GameScene::runStagePlaying(float delta_time) {
enemy.update(delta_time);
}
// Col·lisions primer, després desactivació per fora-de-zona: així una bala que
// Col·lisions primer, després desactivació per fora-de-zone: així una bala que
// el mateix frame xoca amb un enemic i alhora surt del PLAYAREA es compta com a
// impacte abans no se la trenqui per sortir.
runCollisionDetections();
@@ -507,7 +507,7 @@ void GameScene::runStagePlaying(float delta_time) {
void GameScene::runStageLevelCompleted(float delta_time) {
stage_manager_->update(delta_time);
for (uint8_t i = 0; i < 2; i++) {
const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
const bool ACTIU = (i == 0) ? match_config_.player1_active : match_config_.player2_active;
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
ships_[i].processInput(delta_time, i);
ships_[i].update(delta_time);
@@ -580,8 +580,8 @@ void GameScene::drawBullets() const {
void GameScene::drawActiveShipsAlive() const {
for (uint8_t i = 0; i < 2; i++) {
bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) {
bool player_active = (i == 0) ? match_config_.player1_active : match_config_.player2_active;
if (player_active && hit_timer_per_player_[i] == 0.0F) {
ships_[i].draw();
}
}
@@ -613,10 +613,10 @@ void GameScene::drawGameOverState() {
constexpr float SPACING = Defaults::Game::GameOverScreen::TEXT_SPACING;
const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
float centre_x = play_area.x + (play_area.w / 2.0F);
float centre_y = play_area.y + (play_area.h / 2.0F);
float center_x = play_area.x + (play_area.w / 2.0F);
float center_y = play_area.y + (play_area.h / 2.0F);
text_.renderCentered(GAME_OVER_TEXT, {.x = centre_x, .y = centre_y}, SCALE, SPACING);
text_.renderCentered(GAME_OVER_TEXT, {.x = center_x, .y = center_y}, SCALE, SPACING);
drawScoreboard();
}
@@ -663,11 +663,11 @@ void GameScene::drawInitHudState() {
Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboard(), score_progress);
}
if (ship1_progress > 0.0F && match_config_.jugador1_actiu && ships_[0].isActive()) {
if (ship1_progress > 0.0F && match_config_.player1_active && ships_[0].isActive()) {
ships_[0].draw();
}
if (ship2_progress > 0.0F && match_config_.jugador2_actiu && ships_[1].isActive()) {
if (ship2_progress > 0.0F && match_config_.player2_active && ships_[1].isActive()) {
ships_[1].draw();
}
}
@@ -769,20 +769,20 @@ void GameScene::drawScoreboard() {
const float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE;
const float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING;
// Calcular centro de la zona del marcador
// Calcular centro de la zone del marcador
const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD;
float centre_x = scoreboard_zone.w / 2.0F;
float centre_y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F);
float center_x = scoreboard_zone.w / 2.0F;
float center_y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F);
// Renderizar centrat
text_.renderCentered(text, {.x = centre_x, .y = centre_y}, SCALE, SPACING);
text_.renderCentered(text, {.x = center_x, .y = center_y}, SCALE, SPACING);
}
auto GameScene::buildScoreboard() const -> std::string {
// Puntuación P1 (6 dígits) - mostrar zeros si inactiu
std::string score_p1;
std::string vides_p1;
if (match_config_.jugador1_actiu) {
if (match_config_.player1_active) {
score_p1 = std::to_string(score_per_player_[0]);
score_p1 = std::string(6 - std::min(6, static_cast<int>(score_p1.length())), '0') + score_p1;
vides_p1 = (lives_per_player_[0] < 10)
@@ -801,7 +801,7 @@ auto GameScene::buildScoreboard() const -> std::string {
// Puntuación P2 (6 dígits) - mostrar zeros si inactiu
std::string score_p2;
std::string vides_p2;
if (match_config_.jugador2_actiu) {
if (match_config_.player2_active) {
score_p2 = std::to_string(score_per_player_[1]);
score_p2 = std::string(6 - std::min(6, static_cast<int>(score_p2.length())), '0') + score_p2;
vides_p2 = (lives_per_player_[1] < 10)
@@ -890,7 +890,7 @@ void GameScene::drawStageMessage(const std::string& message) {
// ========================================
auto GameScene::getSpawnPoint(uint8_t player_id) const -> Vec2 {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
float x_ratio;
if (match_config_.isSinglePlayer()) {
@@ -904,8 +904,8 @@ auto GameScene::getSpawnPoint(uint8_t player_id) const -> Vec2 {
}
return {
.x = zona.x + (zona.w * x_ratio),
.y = zona.y + (zona.h * Defaults::Game::SPAWN_Y_RATIO)};
.x = zone.x + (zone.w * x_ratio),
.y = zone.y + (zone.h * Defaults::Game::SPAWN_Y_RATIO)};
}
void GameScene::fireBullet(uint8_t player_id) {
@@ -950,10 +950,10 @@ void GameScene::drawContinue() {
float escala_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_SCALE;
float y_ratio_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_Y_RATIO;
float centre_x = play_area.x + (play_area.w / 2.0F);
float center_x = play_area.x + (play_area.w / 2.0F);
float centre_y_continue = play_area.y + (play_area.h * y_ratio_continue);
text_.renderCentered(CONTINUE_TEXT, {.x = centre_x, .y = centre_y_continue}, escala_continue, SPACING);
text_.renderCentered(CONTINUE_TEXT, {.x = center_x, .y = centre_y_continue}, escala_continue, SPACING);
// Countdown number (using constants)
const std::string COUNTER_STR = std::to_string(continue_counter_);
@@ -962,7 +962,7 @@ void GameScene::drawContinue() {
float centre_y_counter = play_area.y + (play_area.h * y_ratio_counter);
text_.renderCentered(COUNTER_STR, {.x = centre_x, .y = centre_y_counter}, escala_counter, SPACING);
text_.renderCentered(COUNTER_STR, {.x = center_x, .y = centre_y_counter}, escala_counter, SPACING);
// "CONTINUES LEFT" (conditional + using constants)
if (!Defaults::Game::INFINITE_CONTINUES) {
@@ -972,16 +972,16 @@ void GameScene::drawContinue() {
float centre_y_info = play_area.y + (play_area.h * y_ratio_info);
text_.renderCentered(CONTINUES_TEXT, {.x = centre_x, .y = centre_y_info}, escala_info, SPACING);
text_.renderCentered(CONTINUES_TEXT, {.x = center_x, .y = centre_y_info}, escala_info, SPACING);
}
}
void GameScene::joinPlayer(uint8_t player_id) {
// Activate player
if (player_id == 0) {
match_config_.jugador1_actiu = true;
match_config_.player1_active = true;
} else {
match_config_.jugador2_actiu = true;
match_config_.player2_active = true;
}
// Reset stats
+15 -15
View File
@@ -22,16 +22,16 @@ using Option = SceneContext::Option;
// Helper: calcular el progrés individual de una letter
// en función del progrés global (efecte seqüencial)
static auto computeLetterProgress(size_t letra_index, size_t num_letras, float global_progress, float threshold) -> float {
if (num_letras == 0) {
static auto computeLetterProgress(size_t letter_index, size_t num_letters, float global_progress, float threshold) -> float {
if (num_letters == 0) {
return 1.0F;
}
// Calcular time per letter
float duration_per_letra = 1.0F / static_cast<float>(num_letras);
float step = threshold * duration_per_letra;
float start = static_cast<float>(letra_index) * step;
float end = start + duration_per_letra;
float duration_per_letter = 1.0F / static_cast<float>(num_letters);
float step = threshold * duration_per_letter;
float start = static_cast<float>(letter_index) * step;
float end = start + duration_per_letter;
// Interpolar progrés
if (global_progress < start) {
@@ -179,7 +179,7 @@ void LogoScene::changeState(AnimationState nou_estat) {
}
auto LogoScene::allLettersComplete() const -> bool {
// Cuando global_progress = 1.0, todas las lletres tenen letra_progress = 1.0
// Cuando global_progress = 1.0, todas las lletres tenen letter_progress = 1.0
return temps_current_state_ >= DURATION_ZOOM;
}
@@ -231,14 +231,14 @@ void LogoScene::update(float delta_time) {
for (size_t i = 0; i < letters_.size() && i < sound_played_.size(); i++) {
if (!sound_played_[i]) {
float letra_progress = computeLetterProgress(
float letter_progress = computeLetterProgress(
i,
letters_.size(),
global_progress,
LETTER_THRESHOLD);
// Reproduir so cuando la letter comença a aparèixer (progress > 0)
if (letra_progress > 0.0F) {
if (letter_progress > 0.0F) {
Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME);
sound_played_[i] = true;
}
@@ -294,28 +294,28 @@ void LogoScene::draw() {
? std::min(temps_current_state_ / DURATION_ZOOM, 1.0F)
: 1.0F; // POST: mantenir al 100%
const Vec2 ORIGEN_ZOOM = {.x = ORIGEN_ZOOM_X, .y = ORIGEN_ZOOM_Y};
const Vec2 ZOOM_ORIGIN = {.x = ZOOM_ORIGIN_X, .y = ZOOM_ORIGIN_Y};
for (size_t i = 0; i < letters_.size(); i++) {
const auto& letter = letters_[i];
float letra_progress = computeLetterProgress(
float letter_progress = computeLetterProgress(
i,
letters_.size(),
global_progress,
LETTER_THRESHOLD);
if (letra_progress <= 0.0F) {
if (letter_progress <= 0.0F) {
continue;
}
Vec2 pos_actual;
pos_actual.x =
ORIGEN_ZOOM.x + ((letter.position.x - ORIGEN_ZOOM.x) * letra_progress);
ZOOM_ORIGIN.x + ((letter.position.x - ZOOM_ORIGIN.x) * letter_progress);
pos_actual.y =
ORIGEN_ZOOM.y + ((letter.position.y - ORIGEN_ZOOM.y) * letra_progress);
ZOOM_ORIGIN.y + ((letter.position.y - ZOOM_ORIGIN.y) * letter_progress);
float t = letra_progress;
float t = letter_progress;
float ease_factor = 1.0F - ((1.0F - t) * (1.0F - t));
float current_scale =
INITIAL_SCALE + ((FINAL_SCALE - INITIAL_SCALE) * ease_factor);
+2 -2
View File
@@ -80,8 +80,8 @@ class LogoScene final : public Scene {
// Constants de animación seqüencial
static constexpr float LETTER_THRESHOLD = 0.6F; // Umbral per activar següent letter (0.0-1.0)
static constexpr float ORIGEN_ZOOM_X = Defaults::Game::WIDTH * 0.5F; // Vec2 inicial X del zoom
static constexpr float ORIGEN_ZOOM_Y = Defaults::Game::HEIGHT * 0.4F; // Vec2 inicial Y del zoom
static constexpr float ZOOM_ORIGIN_X = Defaults::Game::WIDTH * 0.5F; // Vec2 inicial X del zoom
static constexpr float ZOOM_ORIGIN_Y = Defaults::Game::HEIGHT * 0.4F; // Vec2 inicial Y del zoom
// Métodos privats
void initLetters();
+12 -12
View File
@@ -36,8 +36,8 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
text_(sdl.getRenderer()) {
std::cout << "SceneType Titol: Inicialitzant...\n";
match_config_.jugador1_actiu = false;
match_config_.jugador2_actiu = false;
match_config_.player1_active = false;
match_config_.player2_active = false;
match_config_.mode = GameConfig::Mode::NORMAL;
auto option = context_.consumeOption();
@@ -404,8 +404,8 @@ void TitleScene::updatePlayerJoinPhaseState(float delta_time) {
temps_acumulat_ += delta_time;
updateLogoAnimation(delta_time);
const bool P1_ABANS = match_config_.jugador1_actiu;
const bool P2_ABANS = match_config_.jugador2_actiu;
const bool P1_ABANS = match_config_.player1_active;
const bool P2_ABANS = match_config_.player2_active;
if (checkStartGameButtonPressed()) {
context_.setMatchConfig(match_config_);
@@ -466,8 +466,8 @@ void TitleScene::handleStartInput() {
if (!press_start_visible_) {
return;
}
const bool P1_ABANS = match_config_.jugador1_actiu;
const bool P2_ABANS = match_config_.jugador2_actiu;
const bool P1_ABANS = match_config_.player1_active;
const bool P2_ABANS = match_config_.player2_active;
if (!checkStartGameButtonPressed()) {
return;
@@ -492,11 +492,11 @@ void TitleScene::triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_act
if (ship_animator_ == nullptr) {
return;
}
if (match_config_.jugador1_actiu && !p1_was_active) {
if (match_config_.player1_active && !p1_was_active) {
ship_animator_->triggerExitAnimationForPlayer(1);
std::cout << "[TitleScene] P1 " << log_prefix << "ship exiting\n";
}
if (match_config_.jugador2_actiu && !p2_was_active) {
if (match_config_.player2_active && !p2_was_active) {
ship_animator_->triggerExitAnimationForPlayer(2);
std::cout << "[TitleScene] P2 " << log_prefix << "ship exiting\n";
}
@@ -622,14 +622,14 @@ auto TitleScene::checkStartGameButtonPressed() -> bool {
bool any_pressed = false;
for (auto action : START_GAME_BUTTONS) {
if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT)) {
if (!match_config_.jugador1_actiu) {
match_config_.jugador1_actiu = true;
if (!match_config_.player1_active) {
match_config_.player1_active = true;
any_pressed = true;
}
}
if (input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) {
if (!match_config_.jugador2_actiu) {
match_config_.jugador2_actiu = true;
if (!match_config_.player2_active) {
match_config_.player2_active = true;
any_pressed = true;
}
}
+2 -2
View File
@@ -262,8 +262,8 @@ namespace Systems::Collision {
continue;
}
const bool JUGADOR_ACTIU = (player_id == 0)
? ctx.match_config.jugador1_actiu
: ctx.match_config.jugador2_actiu;
? ctx.match_config.player1_active
: ctx.match_config.player2_active;
if (!JUGADOR_ACTIU) {
continue;
}
+79 -79
View File
@@ -11,99 +11,99 @@
#include "game/scenes/game_scene.hpp" // GameOverState (definición completa)
namespace Systems::ContinueScreen {
namespace {
namespace {
// Si el countdown ha bajado de 0, transiciona a GAME_OVER con su timer.
void checkAndApplyTimeout(Context& ctx) {
if (ctx.counter < 0) {
ctx.state = GameOverState::GAME_OVER;
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
}
}
// Si el countdown ha bajado de 0, transiciona a GAME_OVER con su timer.
void checkAndApplyTimeout(Context& ctx) {
if (ctx.counter < 0) {
ctx.state = GameOverState::GAME_OVER;
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
}
}
void revivePlayer(Context& ctx, uint8_t player_id) {
ctx.score_per_player[player_id] = 0;
ctx.lives_per_player[player_id] = Defaults::Game::STARTING_LIVES;
ctx.hit_timer_per_player[player_id] = 0.0F;
void revivePlayer(Context& ctx, uint8_t player_id) {
ctx.score_per_player[player_id] = 0;
ctx.lives_per_player[player_id] = Defaults::Game::STARTING_LIVES;
ctx.hit_timer_per_player[player_id] = 0.0F;
if (player_id == 0) {
ctx.match_config.jugador1_actiu = true;
} else {
ctx.match_config.jugador2_actiu = true;
}
if (player_id == 0) {
ctx.match_config.player1_active = true;
} else {
ctx.match_config.player2_active = true;
}
const Vec2 SPAWN = ctx.get_spawn_point(player_id);
ctx.ships[player_id].init(&SPAWN, /*activar_invulnerabilitat=*/true);
}
const Vec2 SPAWN = ctx.get_spawn_point(player_id);
ctx.ships[player_id].init(&SPAWN, /*activar_invulnerabilitat=*/true);
}
} // namespace
} // namespace
void update(Context& ctx, float delta_time) {
ctx.tick_timer -= delta_time;
if (ctx.tick_timer > 0.0F) {
return;
}
ctx.counter--;
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
checkAndApplyTimeout(ctx);
// Solo pita el tick si seguimos en CONTINUE (no transitamos a GAME_OVER)
if (ctx.state == GameOverState::CONTINUE) {
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
}
}
void processInput(Context& ctx) {
auto* input = Input::get();
const bool P1_START = input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
const bool P2_START = input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
if (P1_START || P2_START) {
// ¿Quedan continues?
if (!Defaults::Game::INFINITE_CONTINUES &&
ctx.continues_used >= Defaults::Game::MAX_CONTINUES) {
ctx.state = GameOverState::GAME_OVER;
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
void update(Context& ctx, float delta_time) {
ctx.tick_timer -= delta_time;
if (ctx.tick_timer > 0.0F) {
return;
}
if (!Defaults::Game::INFINITE_CONTINUES) {
ctx.continues_used++;
}
const uint8_t PRIMARY = P1_START ? 0 : 1;
revivePlayer(ctx, PRIMARY);
// Si ambos pulsan START, revivimos a los dos.
if (P1_START && P2_START) {
revivePlayer(ctx, 1);
}
// Reanudar partida.
ctx.state = GameOverState::NONE;
ctx.counter = 0;
ctx.tick_timer = 0.0F;
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
return;
}
// THRUST/SHOOT acelera el countdown manualmente.
const bool ACCEL = input->checkActionPlayer1(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer2(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT);
if (ACCEL) {
ctx.counter--;
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
checkAndApplyTimeout(ctx);
// Solo pita el tick si seguimos en CONTINUE (no transitamos a GAME_OVER)
if (ctx.state == GameOverState::CONTINUE) {
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
}
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
}
}
void processInput(Context& ctx) {
auto* input = Input::get();
const bool P1_START = input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
const bool P2_START = input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
if (P1_START || P2_START) {
// ¿Quedan continues?
if (!Defaults::Game::INFINITE_CONTINUES &&
ctx.continues_used >= Defaults::Game::MAX_CONTINUES) {
ctx.state = GameOverState::GAME_OVER;
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
return;
}
if (!Defaults::Game::INFINITE_CONTINUES) {
ctx.continues_used++;
}
const uint8_t PRIMARY = P1_START ? 0 : 1;
revivePlayer(ctx, PRIMARY);
// Si ambos pulsan START, revivimos a los dos.
if (P1_START && P2_START) {
revivePlayer(ctx, 1);
}
// Reanudar partida.
ctx.state = GameOverState::NONE;
ctx.counter = 0;
ctx.tick_timer = 0.0F;
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
return;
}
// THRUST/SHOOT acelera el countdown manualmente.
const bool ACCEL = input->checkActionPlayer1(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer2(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT);
if (ACCEL) {
ctx.counter--;
checkAndApplyTimeout(ctx);
if (ctx.state == GameOverState::CONTINUE) {
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
}
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
}
}
} // namespace Systems::ContinueScreen
+1 -1
View File
@@ -31,7 +31,7 @@ namespace Systems::InitHud {
auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 {
const float EASED = Easing::easeOutQuad(progress);
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
// Y inicial: bajo la zona de juego (sale desde fuera).
// Y inicial: bajo la zone de juego (sale desde fuera).
const float Y_INI = zone.y + zone.h + Defaults::Hud::InitAnim::SHIP_SPAWN_Y_OFFSET;
const float Y_ANIM = Y_INI + ((final_position.y - Y_INI) * EASED);
return Vec2{.x = final_position.x, .y = Y_ANIM};
+22 -22
View File
@@ -4,7 +4,7 @@
// Cubre la animación INIT_HUD del comienzo de cada partida/stage:
// 1. Crecimiento de los marcos del PLAYAREA con efecto pincel en 3 fases.
// 2. Marcador subiendo desde abajo.
// 3. Naves entrando desde la zona inferior hacia su spawn.
// 3. Naves entrando desde la zone inferior hacia su spawn.
//
// Todas las funciones son puras (sin estado interno propio). GameScene aporta
// el contexto que necesitan: posiciones finales, texto del scoreboard y el
@@ -21,29 +21,29 @@
namespace Systems::InitHud {
// Convierte un progreso global 0..1 al sub-progreso de un elemento que solo
// se anima en la ventana [ratio_init, ratio_end].
// < ratio_init → 0.0 (no empezó)
// > ratio_end → 1.0 (terminó)
// en rango → interpolación lineal 0..1
[[nodiscard]] auto computeRangeProgress(float global_progress,
float ratio_init,
float ratio_end) -> float;
// Convierte un progreso global 0..1 al sub-progreso de un elemento que solo
// se anima en la ventana [ratio_init, ratio_end].
// < ratio_init → 0.0 (no empezó)
// > ratio_end → 1.0 (terminó)
// en rango → interpolación lineal 0..1
[[nodiscard]] auto computeRangeProgress(float global_progress,
float ratio_init,
float ratio_end) -> float;
// Calcula posición Y animada de una nave durante INIT_HUD. La nave sube
// desde 50 px bajo el PLAYAREA hasta `final_position` con easing.
[[nodiscard]] auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2;
// Calcula posición Y animada de una nave durante INIT_HUD. La nave sube
// desde 50 px bajo el PLAYAREA hasta `final_position` con easing.
[[nodiscard]] auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2;
// Dibuja los 4 lados del PLAYAREA con efecto pincel en 3 fases:
// 0..33% → línea superior crece desde el centro hacia los lados.
// 33..66% → líneas verticales bajan por los laterales.
// 66..100% → línea inferior crece desde los lados hacia el centro.
void drawBordersAnimated(Rendering::Renderer* renderer, float progress);
// Dibuja los 4 lados del PLAYAREA con efecto pincel en 3 fases:
// 0..33% → línea superior crece desde el centro hacia los lados.
// 33..66% → líneas verticales bajan por los laterales.
// 66..100% → línea inferior crece desde los lados hacia el centro.
void drawBordersAnimated(Rendering::Renderer* renderer, float progress);
// Dibuja el scoreboard centrado, subiendo desde fuera de la pantalla
// hasta su posición final con easing.
void drawScoreboardAnimated(const Graphics::VectorText& text,
const std::string& scoreboard_text,
float progress);
// Dibuja el scoreboard centrado, subiendo desde fuera de la pantalla
// hasta su posición final con easing.
void drawScoreboardAnimated(const Graphics::VectorText& text,
const std::string& scoreboard_text,
float progress);
} // namespace Systems::InitHud