Merge branch 'refactor/english-identifiers': identificadors valencians/castellans a anglès

This commit is contained in:
2026-05-24 08:12:56 +02:00
41 changed files with 1008 additions and 1009 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
+34 -34
View File
@@ -1,4 +1,4 @@
// enemies.hpp - Configuració per tipus d'enemic (Pentagon/Cuadrado/Molinillo), spawn i scoring
// enemies.hpp - Configuració per tipus d'enemic (Pentagon/Square/Molinillo), spawn i scoring
// © 2026 JailDesigner
#pragma once
@@ -17,57 +17,57 @@ namespace Defaults::Enemies {
// Pentagon (esquivador - zigzag evasion)
namespace Pentagon {
constexpr float VELOCITAT = 35.0F; // px/s (slightly slower)
constexpr float SPEED = 35.0F; // px/s (slightly slower)
constexpr float MASS = 5.0F; // Masa estándar
constexpr float CANVI_ANGLE_PROB = 0.20F; // 20% per wall hit (frequent zigzag)
constexpr float CANVI_ANGLE_MAX = 1.0F; // Max random angle change (rad)
constexpr float ANGLE_CHANGE_PROB = 0.20F; // 20% per wall hit (frequent zigzag)
constexpr float ANGLE_CHANGE_MAX = 1.0F; // Max random angle change (rad)
constexpr float ZIGZAG_PROB_PER_SECOND = 0.8F; // Probabilidad de zigzag por segundo
constexpr float DROTACIO_MIN = 0.75F; // Min visual rotation (rad/s) [+50%]
constexpr float DROTACIO_MAX = 3.75F; // Max visual rotation (rad/s) [+50%]
constexpr float ROTATION_DELTA_MIN = 0.75F; // Min visual rotation (rad/s) [+50%]
constexpr float ROTATION_DELTA_MAX = 3.75F; // Max visual rotation (rad/s) [+50%]
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
} // namespace Pentagon
// Cuadrado (perseguidor - tracks player)
namespace Cuadrado {
constexpr float VELOCITAT = 40.0F; // px/s (medium speed)
// Square (perseguidor - tracks player)
namespace Square {
constexpr float SPEED = 40.0F; // px/s (medium speed)
constexpr float MASS = 8.0F; // Más pesado, "tanque"
constexpr float TRACKING_STRENGTH = 0.5F; // Interpolation toward player (0.0-1.0)
constexpr float TRACKING_INTERVAL = 1.0F; // Seconds between angle updates
constexpr float DROTACIO_MIN = 0.3F; // Slow rotation [+50%]
constexpr float DROTACIO_MAX = 1.5F; // [+50%]
constexpr float ROTATION_DELTA_MIN = 0.3F; // Slow rotation [+50%]
constexpr float ROTATION_DELTA_MAX = 1.5F; // [+50%]
constexpr const char* SHAPE_FILE = "enemy_square.shp";
} // namespace Cuadrado
} // namespace Square
// Molinillo (agressiu - fast straight lines, proximity spin-up)
namespace Molinillo {
constexpr float VELOCITAT = 50.0F; // px/s (fastest)
namespace Pinwheel {
constexpr float SPEED = 50.0F; // px/s (fastest)
constexpr float MASS = 4.0F; // Más liviano, ágil
constexpr float CANVI_ANGLE_PROB = 0.05F; // 5% per wall hit (rare direction change)
constexpr float CANVI_ANGLE_MAX = 0.3F; // Small angle adjustments
constexpr float DROTACIO_MIN = 3.0F; // Base rotation (rad/s) [+50%]
constexpr float DROTACIO_MAX = 6.0F; // [+50%]
constexpr float DROTACIO_PROXIMITY_MULTIPLIER = 3.0F; // Spin-up multiplier when near ship
constexpr float ANGLE_CHANGE_PROB = 0.05F; // 5% per wall hit (rare direction change)
constexpr float ANGLE_CHANGE_MAX = 0.3F; // Small angle adjustments
constexpr float ROTATION_DELTA_MIN = 3.0F; // Base rotation (rad/s) [+50%]
constexpr float ROTATION_DELTA_MAX = 6.0F; // [+50%]
constexpr float ROTATION_DELTA_PROXIMITY_MULTIPLIER = 3.0F; // Spin-up multiplier when near ship
constexpr float PROXIMITY_DISTANCE = 100.0F; // Distance threshold (px)
constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp";
} // namespace Molinillo
} // namespace Pinwheel
// Animation parameters (shared)
namespace Animation {
// Palpitation
constexpr float PALPITACIO_TRIGGER_PROB = 0.01F; // 1% chance per second
constexpr float PALPITACIO_DURACIO_MIN = 1.0F; // Min duration (seconds)
constexpr float PALPITACIO_DURACIO_MAX = 3.0F; // Max duration (seconds)
constexpr float PALPITACIO_AMPLITUD_MIN = 0.08F; // Min scale variation
constexpr float PALPITACIO_AMPLITUD_MAX = 0.20F; // Max scale variation
constexpr float PALPITACIO_FREQ_MIN = 1.5F; // Min frequency (Hz)
constexpr float PALPITACIO_FREQ_MAX = 3.0F; // Max frequency (Hz)
constexpr float PULSE_TRIGGER_PROB = 0.01F; // 1% chance per second
constexpr float PULSE_DURATION_MIN = 1.0F; // Min duration (seconds)
constexpr float PULSE_DURATION_MAX = 3.0F; // Max duration (seconds)
constexpr float PULSE_AMPLITUD_MIN = 0.08F; // Min scale variation
constexpr float PULSE_AMPLITUD_MAX = 0.20F; // Max scale variation
constexpr float PULSE_FREQ_MIN = 1.5F; // Min frequency (Hz)
constexpr float PULSE_FREQ_MAX = 3.0F; // Max frequency (Hz)
// Rotation acceleration
constexpr float ROTACIO_ACCEL_TRIGGER_PROB = 0.02F; // 2% chance per second [4x more frequent]
constexpr float ROTACIO_ACCEL_DURACIO_MIN = 3.0F; // Min transition time
constexpr float ROTACIO_ACCEL_DURACIO_MAX = 8.0F; // Max transition time
constexpr float ROTACIO_ACCEL_MULTIPLIER_MIN = 0.3F; // Min speed multiplier [more dramatic]
constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 4.0F; // Max speed multiplier [more dramatic]
constexpr float ROTATION_ACCEL_TRIGGER_PROB = 0.02F; // 2% chance per second [4x more frequent]
constexpr float ROTATION_ACCEL_DURATION_MIN = 3.0F; // Min transition time
constexpr float ROTATION_ACCEL_DURATION_MAX = 8.0F; // Max transition time
constexpr float ROTATION_ACCEL_MULTIPLIER_MIN = 0.3F; // Min speed multiplier [more dramatic]
constexpr float ROTATION_ACCEL_MULTIPLIER_MAX = 4.0F; // Max speed multiplier [more dramatic]
} // namespace Animation
// Wounded state (entre primer impacto y explosión)
@@ -94,8 +94,8 @@ namespace Defaults::Enemies {
// Scoring system (puntuación per type de enemy)
namespace Scoring {
constexpr int PENTAGON_SCORE = 100; // Pentágono (esquivador, 35 px/s)
constexpr int QUADRAT_SCORE = 150; // Cuadrado (perseguidor, 40 px/s)
constexpr int MOLINILLO_SCORE = 200; // Molinillo (agressiu, 50 px/s)
constexpr int SQUARE_SCORE = 150; // Square (perseguidor, 40 px/s)
constexpr int PINWHEEL_SCORE = 200; // Molinillo (agressiu, 50 px/s)
} // namespace Scoring
} // namespace Defaults::Enemies
+1 -1
View File
@@ -6,7 +6,7 @@
namespace Defaults::Entities {
constexpr int MAX_ORNIS = 15;
constexpr int MAX_BALES = 50;
constexpr int MAX_BULLETS = 50;
constexpr float SHIP_RADIUS = 12.0F;
constexpr float ENEMY_RADIUS = 20.0F;
+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)
+2 -2
View File
@@ -17,8 +17,8 @@ namespace Defaults::Palette {
constexpr SDL_Color SHIP = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanco neutro
constexpr SDL_Color BULLET = {.r = 155, .g = 255, .b = 175, .a = 255}; // Verde laser
constexpr SDL_Color PENTAGON = {.r = 0, .g = 255, .b = 255, .a = 255}; // Cyan pur "esquivador"
constexpr SDL_Color QUADRAT = {.r = 255, .g = 0, .b = 0, .a = 255}; // Roig pur "tank"
constexpr SDL_Color MOLINILLO = {.r = 255, .g = 0, .b = 255, .a = 255}; // Magenta pur "agressiu"
constexpr SDL_Color SQUARE = {.r = 255, .g = 0, .b = 0, .a = 255}; // Roig pur "tank"
constexpr SDL_Color PINWHEEL = {.r = 255, .g = 0, .b = 255, .a = 255}; // Magenta pur "agressiu"
constexpr SDL_Color WOUNDED = {.r = 255, .g = 220, .b = 60, .a = 255}; // Dorado: enemigo herido
} // namespace Defaults::Palette
+7 -7
View File
@@ -28,11 +28,11 @@ namespace Defaults::Physics {
// Explosions (debris physics)
namespace Debris {
constexpr float VELOCITAT_BASE = 80.0F; // Velocidad inicial (px/s)
constexpr float VARIACIO_VELOCITAT = 40.0F; // ±variació aleatòria (px/s)
constexpr float SPEED_BASE = 80.0F; // Velocidad inicial (px/s)
constexpr float VARIACIO_SPEED = 40.0F; // ±variació aleatòria (px/s)
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²)
constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s)
constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s)
constexpr float ROTATION_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s)
constexpr float ROTATION_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s)
constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa
constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION)
constexpr float SHRINK_RATE = 1.0F; // Reducció de mida (1.0 = encoge a 0 al final del min_lifetime)
@@ -48,8 +48,8 @@ namespace Defaults::Physics {
constexpr float RESTITUTION_BOUNDS = 0.7F;
// Herència de velocity angular (trayectorias curvas)
constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat
constexpr float FACTOR_HERENCIA_MAX = 1.0F; // Màxim 100% del drotacio heredat
constexpr float INHERITANCE_FACTOR_MIN = 0.7F; // Mínimo 70% del drotacio heredat
constexpr float INHERITANCE_FACTOR_MAX = 1.0F; // Màxim 100% del drotacio heredat
constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²)
// Velocity heredada de la nau a l'explosió (80% del feel original).
@@ -68,7 +68,7 @@ namespace Defaults::Physics {
// Angular velocity sin for trajectory inheritance
// Excess above this threshold is converted to tangential linear velocity
// Prevents "vortex trap" problem with high-rotation enemies
constexpr float VELOCITAT_ROT_MAX = 1.5F; // rad/s (~86°/s)
constexpr float SPEED_ROT_MAX = 1.5F; // rad/s (~86°/s)
} // namespace Debris
} // namespace Defaults::Physics
+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);
+5 -5
View File
@@ -235,19 +235,19 @@ namespace Graphics {
}
}
void VectorText::renderCentered(const std::string& text, const Vec2& centre_punt, float scale, float spacing, float brightness, SDL_Color color) const {
void VectorText::renderCentered(const std::string& text, const Vec2& centre_point, float scale, float spacing, float brightness, SDL_Color color) const {
// Calcular dimensions del text
float text_width = getTextWidth(text, scale, spacing);
float text_height = getTextHeight(scale);
// Calcular posición de l'esquina superior izquierda
// restant la meitat de las dimensions del point central
Vec2 posicio_esquerra = {
.x = centre_punt.x - (text_width / 2.0F),
.y = centre_punt.y - (text_height / 2.0F)};
Vec2 top_left_position = {
.x = centre_point.x - (text_width / 2.0F),
.y = centre_point.y - (text_height / 2.0F)};
// Delegar al método render() existent
render(text, posicio_esquerra, scale, spacing, brightness, color);
render(text, top_left_position, scale, spacing, brightness, color);
}
auto VectorText::getTextWidth(const std::string& text, float scale, float spacing) -> float {
+2 -2
View File
@@ -31,12 +31,12 @@ namespace Graphics {
// Renderizar string centrado en un punto
// - text: cadena a renderizar
// - centre_punt: punto central del texto (no esquina superior izquierda)
// - centre_point: punto central del texto (no esquina superior izquierda)
// - scale: factor de scale (1.0 = 20×40 px por carácter)
// - spacing: espacio entre caracteres en píxeles (a scale 1.0)
// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness)
// - color: color RGBA explícit; si alpha==0 (default) s'usa l'oscil·lador global
void renderCentered(const std::string& text, const Vec2& centre_punt, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F, SDL_Color color = {0, 0, 0, 0}) const;
void renderCentered(const std::string& text, const Vec2& centre_point, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F, SDL_Color color = {0, 0, 0, 0}) const;
// Calcular ancho total de un string (útil para centrado).
// Es estático: no depende del estado del VectorText (el ancho viene de
+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 {
+15 -15
View File
@@ -4,52 +4,52 @@
namespace GameConfig {
// Mode de juego
enum class Mode : std::uint8_t {
// 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
+19 -19
View File
@@ -7,40 +7,40 @@
namespace Constants {
// Límits de objectes
constexpr int MAX_ORNIS = Defaults::Entities::MAX_ORNIS;
constexpr int MAX_BALES = Defaults::Entities::MAX_BALES;
constexpr int MAX_BULLETS = Defaults::Entities::MAX_BULLETS;
// 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
+1 -1
View File
@@ -39,7 +39,7 @@ namespace Effects {
// Política: viu sempre durant min_lifetime, després mor quan
// |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els
// fragments ràpids no "popen" en moviment.
float temps_vida; // Temps transcorregut (segons)
float elapsed_time; // Temps transcorregut (segons)
float min_lifetime; // Temps mínim garantit (segons)
bool active; // Està actiu?
+12 -12
View File
@@ -135,7 +135,7 @@ namespace Effects {
float speed =
velocitat_base +
(((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
Defaults::Physics::Debris::VARIACIO_VELOCITAT);
Defaults::Physics::Debris::VARIACIO_SPEED);
debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x;
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
debris->acceleration = friction;
@@ -150,7 +150,7 @@ namespace Effects {
// Vida i shrinking — min_lifetime és el temps mínim garantit; després
// el fragment mor quan |velocity| < MIN_SPEED_TO_DIE.
debris->temps_vida = 0.0F;
debris->elapsed_time = 0.0F;
debris->min_lifetime = lifetime;
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
@@ -170,16 +170,16 @@ namespace Effects {
// FASE 1: Aplicar herència i variació
float factor_herencia =
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN +
Defaults::Physics::Debris::INHERITANCE_FACTOR_MIN +
((std::rand() / static_cast<float>(RAND_MAX)) *
(Defaults::Physics::Debris::FACTOR_HERENCIA_MAX -
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN));
(Defaults::Physics::Debris::INHERITANCE_FACTOR_MAX -
Defaults::Physics::Debris::INHERITANCE_FACTOR_MIN));
float velocitat_ang_heretada = velocitat_angular * factor_herencia;
float variacio = ((std::rand() / static_cast<float>(RAND_MAX)) * 0.2F) - 0.1F;
velocitat_ang_heretada *= (1.0F + variacio);
// FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial
constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX;
constexpr float CAP = Defaults::Physics::Debris::SPEED_ROT_MAX;
float abs_ang = std::abs(velocitat_ang_heretada);
float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F;
@@ -213,10 +213,10 @@ namespace Effects {
// Rotación visual aleatòria (factor = 0.0 o sin velocidad angular)
debris.velocitat_rot_visual =
Defaults::Physics::Debris::ROTACIO_MIN +
Defaults::Physics::Debris::ROTATION_MIN +
((std::rand() / static_cast<float>(RAND_MAX)) *
(Defaults::Physics::Debris::ROTACIO_MAX -
Defaults::Physics::Debris::ROTACIO_MIN));
(Defaults::Physics::Debris::ROTATION_MAX -
Defaults::Physics::Debris::ROTATION_MIN));
// 50% probabilitat de rotación en sentit contrari
if (std::rand() % 2 == 0) {
@@ -266,12 +266,12 @@ namespace Effects {
}
// 1. Actualitzar time de vida
debris.temps_vida += delta_time;
debris.elapsed_time += delta_time;
// Política de mort: viu sí o sí durant min_lifetime; després mor
// quan la velocity cau per sota d'un llindar. Així els fragments
// ràpids no desapareixen en moviment.
if (debris.temps_vida >= debris.min_lifetime) {
if (debris.elapsed_time >= debris.min_lifetime) {
const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) +
(debris.velocity.y * debris.velocity.y);
if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) {
@@ -344,7 +344,7 @@ namespace Effects {
// 6. Shrink lineal sobre la longitud ORIGINAL (no iteratiu).
// SHRINK_T va de 0 a 1 al llarg de min_lifetime; després queda
// a 1 i el shrink_factor manté el valor mínim (1 - factor_shrink).
const float SHRINK_T = std::min(debris.temps_vida / debris.min_lifetime, 1.0F);
const float SHRINK_T = std::min(debris.elapsed_time / debris.min_lifetime, 1.0F);
const float SHRINK_FACTOR = std::max(0.0F, 1.0F - (debris.factor_shrink * SHRINK_T));
// 7. Reconstruir p1/p2 des de la geometria autoritaritzada:
+2 -2
View File
@@ -16,7 +16,7 @@ namespace Effects {
// tail = head velocity_normalitzada × current_length.
//
// Cicle de vida:
// Fase 1 (temps_vida < grow_duration): current_length creix linealment
// Fase 1 (elapsed_time < grow_duration): current_length creix linealment
// de 0 a max_length. Brillor al màxim.
// Fase 2: current_length = max_length × (speed/initial_speed) i brillor
// amb la mateixa proporció. Mor quan length o brightness cauen sota
@@ -30,7 +30,7 @@ namespace Effects {
float max_length; // Longitud màxima (final de la fase de creixement)
float grow_duration; // Temps de creixement de 0 a max_length (s)
float temps_vida; // Acumulador (s)
float elapsed_time; // Acumulador (s)
float initial_speed; // Speed inicial per a la proporció de fase 2
float brightness; // 0..1
+7 -7
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;
@@ -102,7 +102,7 @@ namespace Effects {
fw->max_length = Defaults::FX::Firework::MAX_LENGTH;
fw->grow_duration = Defaults::FX::Firework::GROW_DURATION;
fw->temps_vida = 0.0F;
fw->elapsed_time = 0.0F;
fw->initial_speed = SPEED;
fw->brightness = initial_brightness;
@@ -119,7 +119,7 @@ namespace Effects {
continue;
}
fw.temps_vida += delta_time;
fw.elapsed_time += delta_time;
// 1. Fricció lineal (aplicar en la direcció del movement).
const float SPEED = std::sqrt(
@@ -144,9 +144,9 @@ namespace Effects {
bounceOffPlayArea(fw.head, fw.velocity);
// 4. Calcular longitud i brillor segons fase.
if (fw.temps_vida < fw.grow_duration) {
if (fw.elapsed_time < fw.grow_duration) {
// Fase 1: creixement lineal de 0 a max_length.
const float T = fw.temps_vida / fw.grow_duration;
const float T = fw.elapsed_time / fw.grow_duration;
fw.current_length = fw.max_length * T;
fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS;
} else {
+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,
+6 -6
View File
@@ -9,9 +9,9 @@
namespace Effects {
// FloatingScore: text animat que muestra points guanyats
// S'activa cuando es destrueix un enemy i s'esvaeix después de un time
struct FloatingScore {
// FloatingScore: text animat que muestra points guanyats
// S'activa cuando es destrueix un enemy i s'esvaeix después de un time
struct FloatingScore {
// Text a mostrar (e.g., "100", "150", "200")
std::string text;
@@ -22,12 +22,12 @@ struct FloatingScore {
Vec2 velocity; // px/s (normalment sin amunt: {0.0f, -30.0f})
// Animación de fade
float temps_vida; // Temps transcorregut (segons)
float temps_max; // Temps de vida màxim (segons)
float elapsed_time; // Temps transcorregut (segons)
float max_lifetime; // Temps de vida màxim (segons)
float brightness; // Brillantor calculada (0.0-1.0)
// Estat
bool active;
};
};
} // namespace Effects
+19 -19
View File
@@ -7,15 +7,15 @@
namespace Effects {
FloatingScoreManager::FloatingScoreManager(Rendering::Renderer* renderer)
FloatingScoreManager::FloatingScoreManager(Rendering::Renderer* renderer)
: text_(renderer) {
// Inicialitzar todos los slots como inactius
for (auto& pf : pool_) {
pf.active = false;
}
}
}
void FloatingScoreManager::crear(int points, const Vec2& position) {
void FloatingScoreManager::crear(int points, const Vec2& position) {
// 1. Trobar slot lliure
FloatingScore* pf = findFreeSlot();
if (pf == nullptr) {
@@ -27,13 +27,13 @@ void FloatingScoreManager::crear(int points, const Vec2& position) {
pf->position = position;
pf->velocity = {.x = Defaults::FloatingScore::VELOCITY_X,
.y = Defaults::FloatingScore::VELOCITY_Y};
pf->temps_vida = 0.0F;
pf->temps_max = Defaults::FloatingScore::LIFETIME;
pf->elapsed_time = 0.0F;
pf->max_lifetime = Defaults::FloatingScore::LIFETIME;
pf->brightness = 1.0F;
pf->active = true;
}
}
void FloatingScoreManager::update(float delta_time) {
void FloatingScoreManager::update(float delta_time) {
for (auto& pf : pool_) {
if (!pf.active) {
continue;
@@ -44,20 +44,20 @@ void FloatingScoreManager::update(float delta_time) {
pf.position.y += pf.velocity.y * delta_time;
// 2. Actualitzar time de vida
pf.temps_vida += delta_time;
pf.elapsed_time += delta_time;
// 3. Calcular brightness (fade lineal)
float progress = pf.temps_vida / pf.temps_max; // 0.0 → 1.0
float progress = pf.elapsed_time / pf.max_lifetime; // 0.0 → 1.0
pf.brightness = 1.0F - progress; // 1.0 → 0.0
// 4. Desactivar cuando acaba el time
if (pf.temps_vida >= pf.temps_max) {
if (pf.elapsed_time >= pf.max_lifetime) {
pf.active = false;
}
}
}
}
void FloatingScoreManager::draw() {
void FloatingScoreManager::draw() {
for (const auto& pf : pool_) {
if (!pf.active) {
continue;
@@ -69,15 +69,15 @@ void FloatingScoreManager::draw() {
text_.renderCentered(pf.text, pf.position, SCALE, SPACING, pf.brightness);
}
}
}
void FloatingScoreManager::reset() {
void FloatingScoreManager::reset() {
for (auto& pf : pool_) {
pf.active = false;
}
}
}
auto FloatingScoreManager::getActiveCount() const -> int {
auto FloatingScoreManager::getActiveCount() const -> int {
int count = 0;
for (const auto& pf : pool_) {
if (pf.active) {
@@ -85,15 +85,15 @@ auto FloatingScoreManager::getActiveCount() const -> int {
}
}
return count;
}
}
auto FloatingScoreManager::findFreeSlot() -> FloatingScore* {
auto FloatingScoreManager::findFreeSlot() -> FloatingScore* {
for (auto& pf : pool_) {
if (!pf.active) {
return &pf;
}
}
return nullptr; // Pool ple
}
}
} // namespace Effects
+2 -2
View File
@@ -53,7 +53,7 @@ void Bullet::init() {
body_.clearAccumulators();
}
void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id) {
// Activar bullet
is_active_ = true;
@@ -80,7 +80,7 @@ void Bullet::disparar(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
@@ -17,7 +17,7 @@ class Bullet : public Entities::Entity {
explicit Bullet(Rendering::Renderer* renderer);
void init() override;
void disparar(const Vec2& position, float angle, uint8_t owner_id);
void fire(const Vec2& position, float angle, uint8_t owner_id);
void update(float delta_time) override;
void postUpdate(float delta_time) override;
void draw() const override;
+110 -110
View File
@@ -41,7 +41,7 @@ namespace {
Enemy::Enemy(Rendering::Renderer* renderer)
: Entity(renderer),
tracking_strength_(Defaults::Enemies::Cuadrado::TRACKING_STRENGTH) {
tracking_strength_(Defaults::Enemies::Square::TRACKING_STRENGTH) {
brightness_ = Defaults::Brightness::ENEMIC;
// Configuración del cuerpo físico — defaults para enemy genérico.
@@ -58,43 +58,43 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
const char* shape_file = nullptr;
float base_speed = 0.0F;
float drotacio_min = 0.0F;
float drotacio_max = 0.0F;
float rotation_delta_min = 0.0F;
float rotation_delta_max = 0.0F;
float type_mass = Defaults::Enemies::Body::DEFAULT_MASS;
switch (type_) {
case EnemyType::PENTAGON:
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
base_speed = Defaults::Enemies::Pentagon::SPEED;
rotation_delta_min = Defaults::Enemies::Pentagon::ROTATION_DELTA_MIN;
rotation_delta_max = Defaults::Enemies::Pentagon::ROTATION_DELTA_MAX;
type_mass = Defaults::Enemies::Pentagon::MASS;
break;
case EnemyType::QUADRAT:
shape_file = Defaults::Enemies::Cuadrado::SHAPE_FILE;
base_speed = Defaults::Enemies::Cuadrado::VELOCITAT;
drotacio_min = Defaults::Enemies::Cuadrado::DROTACIO_MIN;
drotacio_max = Defaults::Enemies::Cuadrado::DROTACIO_MAX;
type_mass = Defaults::Enemies::Cuadrado::MASS;
case EnemyType::SQUARE:
shape_file = Defaults::Enemies::Square::SHAPE_FILE;
base_speed = Defaults::Enemies::Square::SPEED;
rotation_delta_min = Defaults::Enemies::Square::ROTATION_DELTA_MIN;
rotation_delta_max = Defaults::Enemies::Square::ROTATION_DELTA_MAX;
type_mass = Defaults::Enemies::Square::MASS;
tracking_timer_ = 0.0F;
break;
case EnemyType::MOLINILLO:
shape_file = Defaults::Enemies::Molinillo::SHAPE_FILE;
base_speed = Defaults::Enemies::Molinillo::VELOCITAT;
drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN;
drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX;
type_mass = Defaults::Enemies::Molinillo::MASS;
case EnemyType::PINWHEEL:
shape_file = Defaults::Enemies::Pinwheel::SHAPE_FILE;
base_speed = Defaults::Enemies::Pinwheel::SPEED;
rotation_delta_min = Defaults::Enemies::Pinwheel::ROTATION_DELTA_MIN;
rotation_delta_max = Defaults::Enemies::Pinwheel::ROTATION_DELTA_MAX;
type_mass = Defaults::Enemies::Pinwheel::MASS;
break;
default:
std::cerr << "[Enemy] Error: tipo desconocido ("
<< static_cast<int>(type_) << "), usando PENTAGON\n";
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
base_speed = Defaults::Enemies::Pentagon::SPEED;
rotation_delta_min = Defaults::Enemies::Pentagon::ROTATION_DELTA_MIN;
rotation_delta_max = Defaults::Enemies::Pentagon::ROTATION_DELTA_MAX;
break;
}
@@ -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 {
@@ -152,28 +152,28 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
body_.clearAccumulators();
// Rotación visual aleatoria (independiente del body)
const float DROTACIO_RANGE = drotacio_max - drotacio_min;
drotacio_ = drotacio_min + ((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DROTACIO_RANGE);
rotacio_ = 0.0F;
const float ROTATION_DELTA_RANGE = rotation_delta_max - rotation_delta_min;
rotation_delta_ = rotation_delta_min + ((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * ROTATION_DELTA_RANGE);
rotation_ = 0.0F;
// Estado de animación
animacio_ = EnemyAnimation();
animacio_.drotacio_base = drotacio_;
animacio_.drotacio_objetivo = drotacio_;
animacio_.drotacio_t = 1.0F;
animation_ = EnemyAnimation();
animation_.rotation_delta_base = rotation_delta_;
animation_.rotation_delta_target = rotation_delta_;
animation_.rotation_delta_t = 1.0F;
// Invulnerabilidad post-spawn
timer_invulnerabilitat_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
invulnerability_timer_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
// Timer para próximo cambio de dirección (Pentagon)
direction_change_timer_ = 0.0F;
esta_ = true;
is_active_ = true;
}
void Enemy::update(float delta_time) {
if (!esta_) {
if (!is_active_) {
return;
}
@@ -189,11 +189,11 @@ void Enemy::update(float delta_time) {
}
// Decremento de invulnerabilidad + LERP de brightness
if (timer_invulnerabilitat_ > 0.0F) {
timer_invulnerabilitat_ -= delta_time;
timer_invulnerabilitat_ = std::max(timer_invulnerabilitat_, 0.0F);
if (invulnerability_timer_ > 0.0F) {
invulnerability_timer_ -= delta_time;
invulnerability_timer_ = std::max(invulnerability_timer_, 0.0F);
const float T_INV = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
const float T_INV = invulnerability_timer_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
const float T = 1.0F - T_INV;
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
@@ -209,11 +209,11 @@ void Enemy::update(float delta_time) {
case EnemyType::PENTAGON:
behaviorPentagon(delta_time);
break;
case EnemyType::QUADRAT:
behaviorQuadrat(delta_time);
case EnemyType::SQUARE:
behaviorSquare(delta_time);
break;
case EnemyType::MOLINILLO:
behaviorMolinillo(delta_time);
case EnemyType::PINWHEEL:
behaviorPinwheel(delta_time);
break;
}
}
@@ -222,18 +222,18 @@ void Enemy::update(float delta_time) {
updateAnimation(delta_time);
// Rotación visual (decoración, no afecta movimiento)
rotacio_ += drotacio_ * delta_time;
rotation_ += rotation_delta_ * delta_time;
}
void Enemy::postUpdate(float /*delta_time*/) {
// Sincronizar mirror tras la integración del world.
if (esta_) {
if (is_active_) {
center_ = body_.position;
}
}
void Enemy::draw() const {
if (!esta_ || !shape_) {
if (!is_active_ || !shape_) {
return;
}
const float SCALE = computeCurrentScale();
@@ -242,11 +242,11 @@ void Enemy::draw() const {
case EnemyType::PENTAGON:
color = Defaults::Palette::PENTAGON;
break;
case EnemyType::QUADRAT:
color = Defaults::Palette::QUADRAT;
case EnemyType::SQUARE:
color = Defaults::Palette::SQUARE;
break;
case EnemyType::MOLINILLO:
color = Defaults::Palette::MOLINILLO;
case EnemyType::PINWHEEL:
color = Defaults::Palette::PINWHEEL;
break;
}
@@ -260,11 +260,11 @@ void Enemy::draw() const {
}
}
Rendering::renderShape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, color);
Rendering::renderShape(renderer_, shape_, center_, rotation_, SCALE, 1.0F, brightness_, color);
}
void Enemy::destruir() {
esta_ = false;
void Enemy::destroy() {
is_active_ = false;
body_.velocity = Vec2{};
body_.angular_velocity = 0.0F;
body_.radius = 0.0F; // No colisiona mientras está inactivo
@@ -273,7 +273,7 @@ void Enemy::destruir() {
last_hit_by_ = 0xFF;
}
void Enemy::herir(uint8_t shooter_id) {
void Enemy::hurt(uint8_t shooter_id) {
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
last_hit_by_ = shooter_id;
// El so HIT ara el reprodueix la bala quan es trenca en debris
@@ -312,7 +312,7 @@ void Enemy::behaviorPentagon(float delta_time) {
if (RAND_VAL < Defaults::Enemies::Pentagon::ZIGZAG_PROB_PER_SECOND * delta_time) {
const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
const float DELTA = (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) *
Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
Defaults::Enemies::Pentagon::ANGLE_CHANGE_MAX;
const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
const float SPEED = body_.velocity.length();
setVelocityFromAngle(NEW_ANGLE, SPEED);
@@ -320,12 +320,12 @@ void Enemy::behaviorPentagon(float delta_time) {
}
}
// QUADRAT: tracking discreto cada TRACKING_INTERVAL. Ajusta dirección
// SQUARE: tracking discreto cada TRACKING_INTERVAL. Ajusta dirección
// hacia el ship mezclando con tracking_strength_.
void Enemy::behaviorQuadrat(float delta_time) {
void Enemy::behaviorSquare(float delta_time) {
tracking_timer_ += delta_time;
if (tracking_timer_ >= Defaults::Enemies::Cuadrado::TRACKING_INTERVAL && ship_position_ != nullptr) {
if (tracking_timer_ >= Defaults::Enemies::Square::TRACKING_INTERVAL && ship_position_ != nullptr) {
tracking_timer_ = 0.0F;
const Vec2 TO_SHIP = *ship_position_ - center_;
@@ -348,89 +348,89 @@ void Enemy::behaviorQuadrat(float delta_time) {
}
}
// MOLINILLO: movimiento recto + boost de rotación visual cerca del ship.
// PINWHEEL: movimiento recto + boost de rotación visual cerca del ship.
// Sin tracking — solo cambios de dirección raros (igual que Pentagon pero
// con probabilidad mucho menor).
void Enemy::behaviorMolinillo(float /*delta_time*/) {
void Enemy::behaviorPinwheel(float /*delta_time*/) {
// Boost de rotación visual por proximidad al ship
if (ship_position_ != nullptr) {
const Vec2 TO_SHIP = *ship_position_ - center_;
const float DIST = TO_SHIP.length();
if (DIST < Defaults::Enemies::Molinillo::PROXIMITY_DISTANCE) {
drotacio_ = animacio_.drotacio_base * Defaults::Enemies::Molinillo::DROTACIO_PROXIMITY_MULTIPLIER;
if (DIST < Defaults::Enemies::Pinwheel::PROXIMITY_DISTANCE) {
rotation_delta_ = animation_.rotation_delta_base * Defaults::Enemies::Pinwheel::ROTATION_DELTA_PROXIMITY_MULTIPLIER;
} else {
drotacio_ = animacio_.drotacio_base;
rotation_delta_ = animation_.rotation_delta_base;
}
}
// Movimiento lineal puro: el world se encarga de integrar y rebotar.
}
void Enemy::updateAnimation(float delta_time) {
updatePalpitation(delta_time);
updatePulse(delta_time);
updateRotationAcceleration(delta_time);
}
void Enemy::updatePalpitation(float delta_time) {
if (animacio_.palpitacio_activa) {
animacio_.palpitacio_fase += 2.0F * Constants::PI * animacio_.palpitacio_frequencia * delta_time;
animacio_.palpitacio_temps_restant -= delta_time;
if (animacio_.palpitacio_temps_restant <= 0.0F) {
animacio_.palpitacio_activa = false;
void Enemy::updatePulse(float delta_time) {
if (animation_.pulse_active) {
animation_.pulse_phase += 2.0F * Constants::PI * animation_.pulse_frequency * delta_time;
animation_.pulse_time_remaining -= delta_time;
if (animation_.pulse_time_remaining <= 0.0F) {
animation_.pulse_active = false;
}
} else {
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
const float TRIGGER_PROB = Defaults::Enemies::Animation::PALPITACIO_TRIGGER_PROB * delta_time;
const float TRIGGER_PROB = Defaults::Enemies::Animation::PULSE_TRIGGER_PROB * delta_time;
if (RAND_VAL < TRIGGER_PROB) {
animacio_.palpitacio_activa = true;
animacio_.palpitacio_fase = 0.0F;
animation_.pulse_active = true;
animation_.pulse_phase = 0.0F;
const float FREQ_RANGE = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX -
Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN;
animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN +
const float FREQ_RANGE = Defaults::Enemies::Animation::PULSE_FREQ_MAX -
Defaults::Enemies::Animation::PULSE_FREQ_MIN;
animation_.pulse_frequency = Defaults::Enemies::Animation::PULSE_FREQ_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * FREQ_RANGE);
const float AMP_RANGE = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX -
Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN;
animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN +
const float AMP_RANGE = Defaults::Enemies::Animation::PULSE_AMPLITUD_MAX -
Defaults::Enemies::Animation::PULSE_AMPLITUD_MIN;
animation_.pulse_amplitude = Defaults::Enemies::Animation::PULSE_AMPLITUD_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * AMP_RANGE);
const float DUR_RANGE = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX -
Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN;
animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN +
const float DUR_RANGE = Defaults::Enemies::Animation::PULSE_DURATION_MAX -
Defaults::Enemies::Animation::PULSE_DURATION_MIN;
animation_.pulse_time_remaining = Defaults::Enemies::Animation::PULSE_DURATION_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DUR_RANGE);
}
}
}
void Enemy::updateRotationAcceleration(float delta_time) {
if (animacio_.drotacio_t < 1.0F) {
animacio_.drotacio_t += delta_time / animacio_.drotacio_duracio;
if (animacio_.drotacio_t >= 1.0F) {
animacio_.drotacio_t = 1.0F;
animacio_.drotacio_base = animacio_.drotacio_objetivo;
drotacio_ = animacio_.drotacio_base;
if (animation_.rotation_delta_t < 1.0F) {
animation_.rotation_delta_t += delta_time / animation_.rotation_delta_duration;
if (animation_.rotation_delta_t >= 1.0F) {
animation_.rotation_delta_t = 1.0F;
animation_.rotation_delta_base = animation_.rotation_delta_target;
rotation_delta_ = animation_.rotation_delta_base;
} else {
const float T = animacio_.drotacio_t;
const float T = animation_.rotation_delta_t;
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
const float INITIAL = animacio_.drotacio_base;
const float TARGET = animacio_.drotacio_objetivo;
drotacio_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
const float INITIAL = animation_.rotation_delta_base;
const float TARGET = animation_.rotation_delta_target;
rotation_delta_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
}
} else {
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
const float TRIGGER_PROB = Defaults::Enemies::Animation::ROTACIO_ACCEL_TRIGGER_PROB * delta_time;
const float TRIGGER_PROB = Defaults::Enemies::Animation::ROTATION_ACCEL_TRIGGER_PROB * delta_time;
if (RAND_VAL < TRIGGER_PROB) {
animacio_.drotacio_t = 0.0F;
animation_.rotation_delta_t = 0.0F;
const float MULT_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX -
Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN;
const float MULTIPLIER = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN +
const float MULT_RANGE = Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MAX -
Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MIN;
const float MULTIPLIER = Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * MULT_RANGE);
animacio_.drotacio_objetivo = animacio_.drotacio_base * MULTIPLIER;
animation_.rotation_delta_target = animation_.rotation_delta_base * MULTIPLIER;
const float DUR_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MAX -
Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN;
animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN +
const float DUR_RANGE = Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MAX -
Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MIN;
animation_.rotation_delta_duration = Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DUR_RANGE);
}
}
@@ -438,15 +438,15 @@ void Enemy::updateRotationAcceleration(float delta_time) {
auto Enemy::computeCurrentScale() const -> float {
float scale = 1.0F;
if (timer_invulnerabilitat_ > 0.0F) {
const float T_INV = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
if (invulnerability_timer_ > 0.0F) {
const float T_INV = invulnerability_timer_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
const float T = 1.0F - T_INV;
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
scale = START + ((END - START) * SMOOTH_T);
} else if (animacio_.palpitacio_activa) {
scale += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase);
} else if (animation_.pulse_active) {
scale += animation_.pulse_amplitude * std::sin(animation_.pulse_phase);
}
return scale;
}
@@ -454,22 +454,22 @@ auto Enemy::computeCurrentScale() const -> float {
auto Enemy::getBaseVelocity() const -> float {
switch (type_) {
case EnemyType::PENTAGON:
return Defaults::Enemies::Pentagon::VELOCITAT;
case EnemyType::QUADRAT:
return Defaults::Enemies::Cuadrado::VELOCITAT;
case EnemyType::MOLINILLO:
return Defaults::Enemies::Molinillo::VELOCITAT;
return Defaults::Enemies::Pentagon::SPEED;
case EnemyType::SQUARE:
return Defaults::Enemies::Square::SPEED;
case EnemyType::PINWHEEL:
return Defaults::Enemies::Pinwheel::SPEED;
default:
return Defaults::Enemies::Pentagon::VELOCITAT;
return Defaults::Enemies::Pentagon::SPEED;
}
}
auto Enemy::getBaseRotation() const -> float {
return animacio_.drotacio_base != 0.0F ? animacio_.drotacio_base : drotacio_;
return animation_.rotation_delta_base != 0.0F ? animation_.rotation_delta_base : rotation_delta_;
}
void Enemy::setTrackingStrength(float strength) {
if (type_ == EnemyType::QUADRAT) {
if (type_ == EnemyType::SQUARE) {
tracking_strength_ = strength;
}
}
+28 -28
View File
@@ -13,24 +13,24 @@
// Tipo de enemy
enum class EnemyType : uint8_t {
PENTAGON = 0, // Pentágono esquivador (zigzag)
QUADRAT = 1, // Cuadrado perseguidor (tracks ship)
MOLINILLO = 2 // Molinillo agresivo (rápido, girando)
SQUARE = 1, // Square perseguidor (tracks ship)
PINWHEEL = 2 // Molinillo agresivo (rápido, girando)
};
// Estado de animación (palpitación + rotación acelerada)
struct EnemyAnimation {
// Palpitación (efecto respiración)
bool palpitacio_activa = false;
float palpitacio_fase = 0.0F;
float palpitacio_frequencia = 2.0F;
float palpitacio_amplitud = 0.15F;
float palpitacio_temps_restant = 0.0F;
bool pulse_active = false;
float pulse_phase = 0.0F;
float pulse_frequency = 2.0F;
float pulse_amplitude = 0.15F;
float pulse_time_remaining = 0.0F;
// Aceleración de rotación visual (modulación a largo plazo)
float drotacio_base = 0.0F;
float drotacio_objetivo = 0.0F;
float drotacio_t = 0.0F;
float drotacio_duracio = 0.0F;
float rotation_delta_base = 0.0F;
float rotation_delta_target = 0.0F;
float rotation_delta_t = 0.0F;
float rotation_delta_duration = 0.0F;
};
class Enemy : public Entities::Entity {
@@ -46,7 +46,7 @@ class Enemy : public Entities::Entity {
void draw() const override;
// Override: Interfaz de Entity
[[nodiscard]] auto isActive() const -> bool override { return esta_; }
[[nodiscard]] auto isActive() const -> bool override { return is_active_; }
// Override: Interfaz de colisión
[[nodiscard]] auto getCollisionRadius() const -> float override {
@@ -56,14 +56,14 @@ class Enemy : public Entities::Entity {
// poden abatre i el cos físic rebota amb la nau. El damage a la nau
// segueix filtrat per `isInvulnerable()` al detectShipEnemy.
[[nodiscard]] auto isCollidable() const -> bool override {
return esta_;
return is_active_;
}
// Marcar destruido (desactiva el cuerpo físicamente: radius=0)
void destruir();
void destroy();
// Getters
[[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; }
[[nodiscard]] auto getRotationDelta() const -> float { return rotation_delta_; }
[[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; }
// Set ship position reference for tracking behavior
@@ -79,18 +79,18 @@ class Enemy : public Entities::Entity {
// actual del body_.velocity.
void setVelocity(float speed);
void setRotation(float rot) {
drotacio_ = rot;
animacio_.drotacio_base = rot;
rotation_delta_ = rot;
animation_.rotation_delta_base = rot;
}
void setTrackingStrength(float strength);
// Invulnerabilidad
[[nodiscard]] auto isInvulnerable() const -> bool { return timer_invulnerabilitat_ > 0.0F; }
[[nodiscard]] auto getInvulnerabilityTime() const -> float { return timer_invulnerabilitat_; }
[[nodiscard]] auto isInvulnerable() const -> bool { return invulnerability_timer_ > 0.0F; }
[[nodiscard]] auto getInvulnerabilityTime() const -> float { return invulnerability_timer_; }
// Estado "herido": entre primer impacto de bala y explosión diferida.
// shooter_id: id del jugador que herí; 0xFF = sin atribución (cadena, etc.).
void herir(uint8_t shooter_id = 0xFF);
void hurt(uint8_t shooter_id = 0xFF);
[[nodiscard]] auto isWounded() const -> bool { return wounded_timer_ > 0.0F; }
[[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; }
[[nodiscard]] auto woundExpiredThisFrame() const -> bool { return wound_expired_this_frame_; }
@@ -104,12 +104,12 @@ class Enemy : public Entities::Entity {
// Miembros específicos (heredados: renderer_, shape_, center_, angle_, brightness_, body_).
// Inicializados en la declaración: el ctor por defecto deja al enemy en estado "inactivo
// como pentágono", coherente con lo que harán init() o el ctor con renderer al activarlo.
float drotacio_{0.0F}; // Velocidad angular visual (rad/s) — solo decoración, separada de body_.angular_velocity
float rotacio_{0.0F}; // Rotación visual acumulada (no afecta movimiento)
bool esta_{false};
float rotation_delta_{0.0F}; // Velocidad angular visual (rad/s) — solo decoración, separada de body_.angular_velocity
float rotation_{0.0F}; // Rotación visual acumulada (no afecta movimiento)
bool is_active_{false};
EnemyType type_{EnemyType::PENTAGON};
EnemyAnimation animacio_;
EnemyAnimation animation_;
// Comportamiento type-specific
float tracking_timer_{0.0F}; // Quadrat: tiempo desde último update de dirección
@@ -118,7 +118,7 @@ class Enemy : public Entities::Entity {
float direction_change_timer_{0.0F}; // Pentagon: tiempo para próximo cambio de dirección
// Invulnerabilidad post-spawn
float timer_invulnerabilitat_{0.0F};
float invulnerability_timer_{0.0F};
// Estado "herido": timer cuenta atrás; al cruzar 0 se marca expiración.
float wounded_timer_{0.0F};
@@ -127,11 +127,11 @@ class Enemy : public Entities::Entity {
// Métodos privados
void updateAnimation(float delta_time);
void updatePalpitation(float delta_time);
void updatePulse(float delta_time);
void updateRotationAcceleration(float delta_time);
void behaviorPentagon(float delta_time);
void behaviorQuadrat(float delta_time);
void behaviorMolinillo(float delta_time);
void behaviorSquare(float delta_time);
void behaviorPinwheel(float delta_time);
[[nodiscard]] auto computeCurrentScale() const -> float;
// Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy.
static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool;
+5 -5
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
@@ -180,7 +180,7 @@ void Ship::draw() const {
Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, color);
}
void Ship::herir() {
void Ship::hurt() {
hurt_timer_ = Defaults::Ship::Hurt::DURATION;
Audio::get()->playSound(Defaults::Sound::HURT, Audio::Group::GAME);
}
+1 -1
View File
@@ -54,7 +54,7 @@ class Ship : public Entities::Entity {
}
// Estat "ferit": primera col·lisió amb enemic dispara HURT; segona durant HURT mata.
void herir();
void hurt();
[[nodiscard]] auto isHurt() const -> bool { return hurt_timer_ > 0.0F; }
[[nodiscard]] auto getHurtTimer() const -> float { return hurt_timer_; }
+53 -53
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();
}
}
@@ -743,7 +743,7 @@ void GameScene::tocado(uint8_t player_id) {
SHIP_POS,
SHIP_ANGLE,
1.0F,
Defaults::Physics::Debris::VELOCITAT_BASE,
Defaults::Physics::Debris::SPEED_BASE,
SHIP_BRIGHT,
INHERITED_VEL,
0.0F, // sense herència angular
@@ -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) {
@@ -927,15 +927,15 @@ void GameScene::fireBullet(uint8_t player_id) {
float sin_a = std::sin(ship_angle);
float tip_x = (LOCAL_TIP_X * cos_a) - (LOCAL_TIP_Y * sin_a) + ship_centre.x;
float tip_y = (LOCAL_TIP_X * sin_a) + (LOCAL_TIP_Y * cos_a) + ship_centre.y;
Vec2 posicio_dispar = {.x = tip_x, .y = tip_y};
Vec2 fire_position = {.x = tip_x, .y = tip_y};
// Buscar primera bullet inactiva en el pool del player.
// El pool global té MAX_BALES slots per jugador (P1=[0..MAX-1], P2=[MAX..2*MAX-1]).
constexpr int SLOTS_PER_PLAYER = Defaults::Entities::MAX_BALES;
// El pool global té MAX_BULLETS slots per jugador (P1=[0..MAX-1], P2=[MAX..2*MAX-1]).
constexpr int SLOTS_PER_PLAYER = Defaults::Entities::MAX_BULLETS;
const int START_IDX = player_id * SLOTS_PER_PLAYER;
for (int i = START_IDX; i < START_IDX + SLOTS_PER_PLAYER; i++) {
if (!bullets_[i].isActive()) {
bullets_[i].disparar(posicio_dispar, ship_angle, player_id);
bullets_[i].fire(fire_position, ship_angle, player_id);
break;
}
}
@@ -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
+1 -1
View File
@@ -67,7 +67,7 @@ class GameScene final : public Scene {
std::array<Enemy, Constants::MAX_ORNIS> enemies_;
// 6 balas: P1=[0,1,2], P2=[3,4,5]. El cast a size_t evita la
// widening conversion implícita que detecta clang-tidy.
std::array<Bullet, static_cast<std::size_t>(Constants::MAX_BALES) * 2> bullets_;
std::array<Bullet, static_cast<std::size_t>(Constants::MAX_BULLETS) * 2> bullets_;
std::array<float, 2> hit_timer_per_player_; // Death timers per player (seconds)
// Lives and game over system
+96 -97
View File
@@ -20,18 +20,18 @@ using SceneManager::SceneContext;
using SceneType = SceneContext::SceneType;
using Option = SceneContext::Option;
// Helper: calcular el progrés individual de una lletra
// 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 lletra
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;
// Calcular time per letter
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) {
@@ -47,15 +47,14 @@ LogoScene::LogoScene(SDLManager& sdl, SceneContext& context)
: sdl_(sdl),
context_(context),
debris_manager_(std::make_unique<Effects::DebrisManager>(sdl.getRenderer()))
{
debris_manager_(std::make_unique<Effects::DebrisManager>(sdl.getRenderer())) {
std::cout << "SceneType Logo: Inicialitzant...\n";
// Consumir opciones (LOGO no processa opciones actualment)
auto option = context_.consumeOption();
(void)option; // Suprimir warning
so_reproduit_.fill(false); // Inicialitzar seguiment de sons
sound_played_.fill(false); // Inicialitzar seguiment de sons
initLetters();
}
@@ -91,7 +90,7 @@ void LogoScene::initLetters() {
"logo/letra_s.shp"};
// Pas 1: Carregar todas las formes i calcular amplades
float ancho_total = 0.0F;
float total_width = 0.0F;
for (const auto& file : archivos) {
auto shape = ShapeLoader::load(file);
@@ -111,66 +110,66 @@ void LogoScene::initLetters() {
}
}
float ancho_sin_escalar = max_x - min_x;
float width_unscaled = max_x - min_x;
// IMPORTANT: Escalar ancho i offset con ESCALA_FINAL
// IMPORTANT: Escalar ancho i offset con FINAL_SCALE
// per que las posicions finals coincideixin con la mida real de las lletres
float ancho = ancho_sin_escalar * ESCALA_FINAL;
float offset_centre = (shape->getCenter().x - min_x) * ESCALA_FINAL;
float width = width_unscaled * FINAL_SCALE;
float center_offset = (shape->getCenter().x - min_x) * FINAL_SCALE;
lletres_.push_back({shape,
letters_.push_back({shape,
{.x = 0.0F, .y = 0.0F}, // Posición es calcularà después
ancho,
offset_centre});
width,
center_offset});
ancho_total += ancho;
total_width += width;
}
// Pas 2: Añadir espaiat entre lletres
ancho_total += ESPAI_ENTRE_LLETRES * (lletres_.size() - 1);
total_width += LETTER_SPACING * (letters_.size() - 1);
// Pas 3: Calcular posición inicial (centrat horitzontal)
constexpr auto PANTALLA_ANCHO = static_cast<float>(Defaults::Game::WIDTH);
constexpr auto SCREEN_WIDTH = static_cast<float>(Defaults::Game::WIDTH);
constexpr auto PANTALLA_ALTO = static_cast<float>(Defaults::Game::HEIGHT);
float x_inicial = (PANTALLA_ANCHO - ancho_total) / 2.0F;
float x_inicial = (SCREEN_WIDTH - total_width) / 2.0F;
float y_centre = PANTALLA_ALTO / 2.0F;
// Pas 4: Assignar posicions a cada lletra
// Pas 4: Assignar posicions a cada letter
float x_actual = x_inicial;
for (auto& lletra : lletres_) {
for (auto& letter : letters_) {
// Posicionar el centro de la shape (shape_centre) en pantalla
// Usar offset_centre en lloc de ancho/2 perquè shape_centre
// Usar center_offset en lloc de ancho/2 perquè shape_centre
// pot no estar exactament al mig del bounding box
lletra.position.x = x_actual + lletra.offset_centre;
lletra.position.y = y_centre;
letter.position.x = x_actual + letter.center_offset;
letter.position.y = y_centre;
// Avançar para següent lletra
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
// Avançar para següent letter
x_actual += letter.width + LETTER_SPACING;
}
std::cout << "[LogoScene] " << lletres_.size()
<< " lletres carregades, ancho total: " << ancho_total << " px\n";
std::cout << "[LogoScene] " << letters_.size()
<< " lletres carregades, ancho total: " << total_width << " px\n";
}
void LogoScene::changeState(AnimationState nou_estat) {
estat_actual_ = nou_estat;
temps_estat_actual_ = 0.0F; // Reset time
current_state_ = nou_estat;
temps_current_state_ = 0.0F; // Reset time
// Inicialitzar state de explosión
if (nou_estat == AnimationState::EXPLOSION) {
lletra_explosio_index_ = 0;
temps_des_ultima_explosio_ = 0.0F;
letter_explosion_index_ = 0;
time_since_last_explosion_ = 0.0F;
// Generar ordre aleatori de explosions
ordre_explosio_.clear();
for (size_t i = 0; i < lletres_.size(); i++) {
ordre_explosio_.push_back(i);
explosion_order_.clear();
for (size_t i = 0; i < letters_.size(); i++) {
explosion_order_.push_back(i);
}
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(ordre_explosio_.begin(), ordre_explosio_.end(), g);
std::shuffle(explosion_order_.begin(), explosion_order_.end(), g);
} else if (nou_estat == AnimationState::POST_EXPLOSION) {
Audio::get()->playMusic("title.ogg");
}
@@ -180,35 +179,35 @@ void LogoScene::changeState(AnimationState nou_estat) {
}
auto LogoScene::allLettersComplete() const -> bool {
// Cuando global_progress = 1.0, todas las lletres tenen letra_progress = 1.0
return temps_estat_actual_ >= DURACIO_ZOOM;
// Cuando global_progress = 1.0, todas las lletres tenen letter_progress = 1.0
return temps_current_state_ >= DURATION_ZOOM;
}
void LogoScene::updateExplosions(float delta_time) {
temps_des_ultima_explosio_ += delta_time;
time_since_last_explosion_ += delta_time;
// Comprovar si es el moment de explode la següent lletra
if (temps_des_ultima_explosio_ >= DELAY_ENTRE_EXPLOSIONS) {
if (lletra_explosio_index_ < lletres_.size()) {
// Explotar lletra actual (en ordre aleatori)
size_t index_actual = ordre_explosio_[lletra_explosio_index_];
const auto& lletra = lletres_[index_actual];
// Comprovar si es el moment de explode la següent letter
if (time_since_last_explosion_ >= DELAY_ENTRE_EXPLOSIONS) {
if (letter_explosion_index_ < letters_.size()) {
// Explotar letter actual (en ordre aleatori)
size_t index_actual = explosion_order_[letter_explosion_index_];
const auto& letter = letters_[index_actual];
debris_manager_->explode(
lletra.shape, // Forma a explode
lletra.position, // Posición
letter.shape, // Forma a explode
letter.position, // Posición
0.0F, // Angle (sin rotación)
ESCALA_FINAL, // Escala (lletres a scale final)
VELOCITAT_EXPLOSIO, // Velocidad base
FINAL_SCALE, // Escala (lletres a scale final)
SPEED_EXPLOSIO, // Velocidad base
1.0F, // Brightness màxim (per defecte)
{.x = 0.0F, .y = 0.0F} // Sin velocity (per defecte)
);
std::cout << "[LogoScene] Explota lletra " << lletra_explosio_index_ << "\n";
std::cout << "[LogoScene] Explota letter " << letter_explosion_index_ << "\n";
// Passar a la següent lletra
lletra_explosio_index_++;
temps_des_ultima_explosio_ = 0.0F;
// Passar a la següent letter
letter_explosion_index_++;
time_since_last_explosion_ = 0.0F;
} else {
// Todas las lletres han explotat, transición a POST_EXPLOSION
changeState(AnimationState::POST_EXPLOSION);
@@ -217,31 +216,31 @@ void LogoScene::updateExplosions(float delta_time) {
}
void LogoScene::update(float delta_time) {
temps_estat_actual_ += delta_time;
temps_current_state_ += delta_time;
switch (estat_actual_) {
switch (current_state_) {
case AnimationState::PRE_ANIMATION:
if (temps_estat_actual_ >= DURACIO_PRE) {
if (temps_current_state_ >= DURATION_PRE) {
changeState(AnimationState::ANIMATION);
}
break;
case AnimationState::ANIMATION: {
// Reproduir so per cada lletra cuando comença a aparèixer
float global_progress = std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F);
// Reproduir so per cada letter cuando comença a aparèixer
float global_progress = std::min(temps_current_state_ / DURATION_ZOOM, 1.0F);
for (size_t i = 0; i < lletres_.size() && i < so_reproduit_.size(); i++) {
if (!so_reproduit_[i]) {
float letra_progress = computeLetterProgress(
for (size_t i = 0; i < letters_.size() && i < sound_played_.size(); i++) {
if (!sound_played_[i]) {
float letter_progress = computeLetterProgress(
i,
lletres_.size(),
letters_.size(),
global_progress,
THRESHOLD_LETRA);
LETTER_THRESHOLD);
// Reproduir so cuando la lletra comença a aparèixer (progress > 0)
if (letra_progress > 0.0F) {
// Reproduir so cuando la letter comença a aparèixer (progress > 0)
if (letter_progress > 0.0F) {
Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME);
so_reproduit_[i] = true;
sound_played_[i] = true;
}
}
}
@@ -253,7 +252,7 @@ void LogoScene::update(float delta_time) {
}
case AnimationState::POST_ANIMATION:
if (temps_estat_actual_ >= DURACIO_POST_ANIMATION) {
if (temps_current_state_ >= DURATION_POST_ANIMATION) {
changeState(AnimationState::EXPLOSION);
}
break;
@@ -263,7 +262,7 @@ void LogoScene::update(float delta_time) {
break;
case AnimationState::POST_EXPLOSION:
if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) {
if (temps_current_state_ >= DURATION_POST_EXPLOSION) {
// Transición a pantalla de título
context_.setNextScene(SceneType::TITLE);
}
@@ -283,47 +282,47 @@ void LogoScene::draw() {
// Director ha hecho el clear; aquí solo pintamos lo de la escena.
// PRE_ANIMATION: Solo pantalla negra (no se pinta nada).
if (estat_actual_ == AnimationState::PRE_ANIMATION) {
if (current_state_ == AnimationState::PRE_ANIMATION) {
return;
}
// ANIMATION o POST_ANIMATION: Dibuixar lletres con animación
if (estat_actual_ == AnimationState::ANIMATION ||
estat_actual_ == AnimationState::POST_ANIMATION) {
if (current_state_ == AnimationState::ANIMATION ||
current_state_ == AnimationState::POST_ANIMATION) {
float global_progress =
(estat_actual_ == AnimationState::ANIMATION)
? std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F)
(current_state_ == AnimationState::ANIMATION)
? 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 < lletres_.size(); i++) {
const auto& lletra = lletres_[i];
for (size_t i = 0; i < letters_.size(); i++) {
const auto& letter = letters_[i];
float letra_progress = computeLetterProgress(
float letter_progress = computeLetterProgress(
i,
lletres_.size(),
letters_.size(),
global_progress,
THRESHOLD_LETRA);
LETTER_THRESHOLD);
if (letra_progress <= 0.0F) {
if (letter_progress <= 0.0F) {
continue;
}
Vec2 pos_actual;
pos_actual.x =
ORIGEN_ZOOM.x + ((lletra.position.x - ORIGEN_ZOOM.x) * letra_progress);
ZOOM_ORIGIN.x + ((letter.position.x - ZOOM_ORIGIN.x) * letter_progress);
pos_actual.y =
ORIGEN_ZOOM.y + ((lletra.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 =
ESCALA_INICIAL + ((ESCALA_FINAL - ESCALA_INICIAL) * ease_factor);
INITIAL_SCALE + ((FINAL_SCALE - INITIAL_SCALE) * ease_factor);
Rendering::renderShape(
sdl_.getRenderer(),
lletra.shape,
letter.shape,
pos_actual,
0.0F,
current_scale,
@@ -332,24 +331,24 @@ void LogoScene::draw() {
}
// EXPLOSION: Dibuixar solo lletres que aún no han explotat
if (estat_actual_ == AnimationState::EXPLOSION) {
if (current_state_ == AnimationState::EXPLOSION) {
// Crear conjunt de lletres ya explotades
std::set<size_t> explotades;
for (size_t i = 0; i < lletra_explosio_index_; i++) {
explotades.insert(ordre_explosio_[i]);
for (size_t i = 0; i < letter_explosion_index_; i++) {
explotades.insert(explosion_order_[i]);
}
// Dibuixar solo lletres que NO han explotat
for (size_t i = 0; i < lletres_.size(); i++) {
for (size_t i = 0; i < letters_.size(); i++) {
if (!explotades.contains(i)) {
const auto& lletra = lletres_[i];
const auto& letter = letters_[i];
Rendering::renderShape(
sdl_.getRenderer(),
lletra.shape,
lletra.position,
letter.shape,
letter.position,
0.0F,
ESCALA_FINAL,
FINAL_SCALE,
1.0F);
}
}
+22 -22
View File
@@ -42,46 +42,46 @@ class LogoScene final : public Scene {
SDLManager& sdl_;
SceneManager::SceneContext& context_;
AnimationState estat_actual_{AnimationState::PRE_ANIMATION}; // Estat actual de la màquina
AnimationState current_state_{AnimationState::PRE_ANIMATION}; // Estat actual de la màquina
float
temps_estat_actual_{0.0F}; // Temps en l'state actual (reset en cada transición)
temps_current_state_{0.0F}; // Temps en l'state actual (reset en cada transición)
// Gestor de fragments de explosions
std::unique_ptr<Effects::DebrisManager> debris_manager_;
// Seguiment de explosions seqüencials
size_t lletra_explosio_index_{0}; // Índex de la següent lletra a explode
float temps_des_ultima_explosio_{0.0F}; // Temps desde l'última explosión
std::vector<size_t> ordre_explosio_; // Ordre aleatori de índexs de lletres
size_t letter_explosion_index_{0}; // Índex de la següent letter a explode
float time_since_last_explosion_{0.0F}; // Temps desde l'última explosión
std::vector<size_t> explosion_order_; // Ordre aleatori de índexs de lletres
// Estructura para cada lletra del logo
struct LetraLogo {
// Estructura para cada letter del logo
struct LogoLetter {
std::shared_ptr<Graphics::Shape> shape;
Vec2 position; // Posición final en pantalla
float ancho; // Ancho del bounding box
float offset_centre; // Distancia de min_x a shape_centre.x
float width; // Ancho del bounding box
float center_offset; // Distancia de min_x a shape_centre.x
};
std::vector<LetraLogo> lletres_; // 9 lletres: J-A-I-L-G-A-M-E-S
std::vector<LogoLetter> letters_; // 9 lletres: J-A-I-L-G-A-M-E-S
// Seguiment de sons de lletres (evitar reproduccions repetides)
std::array<bool, 9> so_reproduit_; // Track si cada lletra ya ha reproduit el so
std::array<bool, 9> sound_played_; // Track si cada letter ya ha reproduit el so
// Constants de animación
static constexpr float DURACIO_PRE = 1.5F; // Duració PRE_ANIMATION (pantalla negra)
static constexpr float DURACIO_ZOOM = 4.0F; // Duració del zoom (segons)
static constexpr float DURACIO_POST_ANIMATION = 3.0F; // Duració POST_ANIMATION (logo complet)
static constexpr float DURACIO_POST_EXPLOSION = 3.0F; // Duració POST_EXPLOSION (espera final)
static constexpr float DURATION_PRE = 1.5F; // Duració PRE_ANIMATION (pantalla negra)
static constexpr float DURATION_ZOOM = 4.0F; // Duració del zoom (segons)
static constexpr float DURATION_POST_ANIMATION = 3.0F; // Duració POST_ANIMATION (logo complet)
static constexpr float DURATION_POST_EXPLOSION = 3.0F; // Duració POST_EXPLOSION (espera final)
static constexpr float DELAY_ENTRE_EXPLOSIONS = 0.1F; // Temps entre explosions de lletres
static constexpr float VELOCITAT_EXPLOSIO = 240.0F; // Velocidad base fragments (px/s)
static constexpr float ESCALA_INICIAL = 0.1F; // Escala inicial (10%)
static constexpr float ESCALA_FINAL = 0.8F; // Escala final (80%)
static constexpr float ESPAI_ENTRE_LLETRES = 10.0F; // Espaiat entre lletres
static constexpr float SPEED_EXPLOSIO = 240.0F; // Velocidad base fragments (px/s)
static constexpr float INITIAL_SCALE = 0.1F; // Escala inicial (10%)
static constexpr float FINAL_SCALE = 0.8F; // Escala final (80%)
static constexpr float LETTER_SPACING = 10.0F; // Espaiat entre lletres
// Constants de animación seqüencial
static constexpr float THRESHOLD_LETRA = 0.6F; // Umbral per activar següent lletra (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 LETTER_THRESHOLD = 0.6F; // Umbral per activar següent letter (0.0-1.0)
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();
+139 -139
View File
@@ -36,15 +36,15 @@ 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();
if (option == Option::JUMP_TO_TITLE_MAIN) {
std::cout << "SceneType Titol: Opció JUMP_TO_TITLE_MAIN activada\n";
estat_actual_ = TitleState::MAIN;
temps_estat_main_ = 0.0F;
current_state_ = TitleState::MAIN;
state_time_main_ = 0.0F;
}
// Càmera 3D: posicionada a l'origen, mirant cap a +Z, amb Y cap amunt.
@@ -61,7 +61,7 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
camera_.get(),
200);
starfield_->setColor(Defaults::Title::Colors::STARFIELD);
if (estat_actual_ == TitleState::MAIN) {
if (current_state_ == TitleState::MAIN) {
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
} else {
starfield_->setBrightness(0.0F);
@@ -103,7 +103,7 @@ void TitleScene::initTitle() {
"title/letra_n.shp",
"title/letra_i.shp"};
float ancho_total_orni = 0.0F;
float total_width_orni = 0.0F;
for (const auto& file : FITXERS_ORNI) {
auto shape = ShapeLoader::load(file);
if (!shape || !shape->isValid()) {
@@ -122,25 +122,25 @@ void TitleScene::initTitle() {
max_y = std::max(max_y, point.y);
}
}
const float ANCHO = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
const float ALTURA = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
lletres_orni_.push_back({shape, {.x = 0.0F, .y = 0.0F}, ANCHO, ALTURA, OFFSET_CENTRE});
ancho_total_orni += ANCHO;
const float WIDTH = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
const float HEIGHT = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
const float CENTER_OFFSET = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
letters_orni_.push_back({shape, {.x = 0.0F, .y = 0.0F}, WIDTH, HEIGHT, CENTER_OFFSET});
total_width_orni += WIDTH;
}
ancho_total_orni += ESPAI_ENTRE_LLETRES * static_cast<float>(lletres_orni_.size() - 1);
total_width_orni += LETTER_SPACING * static_cast<float>(letters_orni_.size() - 1);
float x_actual = (Defaults::Game::WIDTH - ancho_total_orni) / 2.0F;
for (auto& lletra : lletres_orni_) {
lletra.position.x = x_actual + lletra.offset_centre;
lletra.position.y = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
float x_actual = (Defaults::Game::WIDTH - total_width_orni) / 2.0F;
for (auto& letter : letters_orni_) {
letter.position.x = x_actual + letter.center_offset;
letter.position.y = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
x_actual += letter.width + LETTER_SPACING;
}
const float ALTURA_ORNI = lletres_orni_.empty() ? 50.0F : lletres_orni_[0].altura;
const float ORNI_HEIGHT = letters_orni_.empty() ? 50.0F : letters_orni_[0].height;
const float Y_ORNI = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
const float SEPARACION = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_LINE_SPACING;
y_attack_dinamica_ = Y_ORNI + ALTURA_ORNI + SEPARACION;
dynamic_attack_y_ = Y_ORNI + ORNI_HEIGHT + SEPARACION;
const std::vector<std::string> FITXERS_ATTACK = {
"title/letra_a.shp",
@@ -151,7 +151,7 @@ void TitleScene::initTitle() {
"title/letra_k.shp",
"title/letra_exclamacion.shp"};
float ancho_total_attack = 0.0F;
float total_width_attack = 0.0F;
for (const auto& file : FITXERS_ATTACK) {
auto shape = ShapeLoader::load(file);
if (!shape || !shape->isValid()) {
@@ -170,28 +170,28 @@ void TitleScene::initTitle() {
max_y = std::max(max_y, point.y);
}
}
const float ANCHO = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
const float ALTURA = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
lletres_attack_.push_back({shape, {.x = 0.0F, .y = 0.0F}, ANCHO, ALTURA, OFFSET_CENTRE});
ancho_total_attack += ANCHO;
const float WIDTH = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
const float HEIGHT = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
const float CENTER_OFFSET = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
letters_attack_.push_back({shape, {.x = 0.0F, .y = 0.0F}, WIDTH, HEIGHT, CENTER_OFFSET});
total_width_attack += WIDTH;
}
ancho_total_attack += ESPAI_ENTRE_LLETRES * static_cast<float>(lletres_attack_.size() - 1);
total_width_attack += LETTER_SPACING * static_cast<float>(letters_attack_.size() - 1);
x_actual = (Defaults::Game::WIDTH - ancho_total_attack) / 2.0F;
for (auto& lletra : lletres_attack_) {
lletra.position.x = x_actual + lletra.offset_centre;
lletra.position.y = y_attack_dinamica_;
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
x_actual = (Defaults::Game::WIDTH - total_width_attack) / 2.0F;
for (auto& letter : letters_attack_) {
letter.position.x = x_actual + letter.center_offset;
letter.position.y = dynamic_attack_y_;
x_actual += letter.width + LETTER_SPACING;
}
posicions_originals_orni_.clear();
for (const auto& lletra : lletres_orni_) {
posicions_originals_orni_.push_back(lletra.position);
original_positions_orni_.clear();
for (const auto& letter : letters_orni_) {
original_positions_orni_.push_back(letter.position);
}
posicions_originals_attack_.clear();
for (const auto& lletra : lletres_attack_) {
posicions_originals_attack_.push_back(lletra.position);
original_positions_attack_.clear();
for (const auto& letter : letters_attack_) {
original_positions_attack_.push_back(letter.position);
}
}
@@ -211,8 +211,8 @@ void TitleScene::inicialitzarJailgames() {
constexpr float SCALE = Defaults::Title::Layout::JAILGAMES_SCALE;
float ancho_total = 0.0F;
float altura_max = 0.0F;
float total_width = 0.0F;
float max_height = 0.0F;
for (const auto& file : FITXERS) {
auto shape = ShapeLoader::load(file);
if (!shape || !shape->isValid()) {
@@ -231,28 +231,28 @@ void TitleScene::inicialitzarJailgames() {
max_y = std::max(max_y, point.y);
}
}
const float ANCHO = (max_x - min_x) * SCALE;
const float ALTURA = (max_y - min_y) * SCALE;
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * SCALE;
lletres_jailgames_.push_back({shape, {.x = 0.0F, .y = 0.0F}, ANCHO, ALTURA, OFFSET_CENTRE});
ancho_total += ANCHO;
altura_max = std::max(altura_max, ALTURA);
const float WIDTH = (max_x - min_x) * SCALE;
const float HEIGHT = (max_y - min_y) * SCALE;
const float CENTER_OFFSET = (shape->getCenter().x - min_x) * SCALE;
letters_jailgames_.push_back({shape, {.x = 0.0F, .y = 0.0F}, WIDTH, HEIGHT, CENTER_OFFSET});
total_width += WIDTH;
max_height = std::max(max_height, HEIGHT);
}
constexpr float ESPAI_JAILGAMES = ESPAI_ENTRE_LLETRES * SCALE;
if (!lletres_jailgames_.empty()) {
ancho_total += ESPAI_JAILGAMES * static_cast<float>(lletres_jailgames_.size() - 1);
constexpr float JAILGAMES_SPACING = LETTER_SPACING * SCALE;
if (!letters_jailgames_.empty()) {
total_width += JAILGAMES_SPACING * static_cast<float>(letters_jailgames_.size() - 1);
}
const float Y_COPY = Defaults::Game::HEIGHT * Defaults::Title::Layout::COPYRIGHT1_POS;
const float GAP = Defaults::Game::HEIGHT * Defaults::Title::Layout::JAILGAMES_COPYRIGHT_GAP;
const float Y_CENTRE = Y_COPY - GAP - (altura_max / 2.0F);
const float X_INICIAL = (Defaults::Game::WIDTH - ancho_total) / 2.0F;
const float Y_CENTRE = Y_COPY - GAP - (max_height / 2.0F);
const float X_INICIAL = (Defaults::Game::WIDTH - total_width) / 2.0F;
float x_actual = X_INICIAL;
for (auto& lletra : lletres_jailgames_) {
lletra.position.x = x_actual + lletra.offset_centre;
lletra.position.y = Y_CENTRE;
x_actual += lletra.ancho + ESPAI_JAILGAMES;
for (auto& letter : letters_jailgames_) {
letter.position.x = x_actual + letter.center_offset;
letter.position.y = Y_CENTRE;
x_actual += letter.width + JAILGAMES_SPACING;
}
}
@@ -268,12 +268,12 @@ void TitleScene::dibuixarPeuTitol(float spacing) const {
const float COPYRIGHT_S = std::lerp(S::FOOTER_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_copyright_progress_));
const float JAILGAMES_RENDER_SCALE = Defaults::Title::Layout::JAILGAMES_SCALE * JAILGAMES_S;
for (const auto& lletra : lletres_jailgames_) {
for (const auto& letter : letters_jailgames_) {
const Vec2 POS{
.x = SCREEN_CENTRE_X + (JAILGAMES_S * (lletra.position.x - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (JAILGAMES_S * (lletra.position.y - SCREEN_CENTRE_Y)),
.x = SCREEN_CENTRE_X + (JAILGAMES_S * (letter.position.x - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (JAILGAMES_S * (letter.position.y - SCREEN_CENTRE_Y)),
};
Rendering::renderShape(sdl_.getRenderer(), lletra.shape, POS, 0.0F, JAILGAMES_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::JAILGAMES_LOGO);
Rendering::renderShape(sdl_.getRenderer(), letter.shape, POS, 0.0F, JAILGAMES_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::JAILGAMES_LOGO);
}
std::string copyright = Project::COPYRIGHT;
for (char& c : copyright) {
@@ -297,15 +297,15 @@ void TitleScene::update(float delta_time) {
starfield_->update(delta_time);
}
if (ship_animator_ &&
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
estat_actual_ == TitleState::STARFIELD ||
estat_actual_ == TitleState::MAIN ||
estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) {
(current_state_ == TitleState::STARFIELD_FADE_IN ||
current_state_ == TitleState::STARFIELD ||
current_state_ == TitleState::MAIN ||
current_state_ == TitleState::PLAYER_JOIN_PHASE)) {
ship_animator_->update(delta_time);
}
updateFlashes(delta_time);
switch (estat_actual_) {
switch (current_state_) {
case TitleState::STARFIELD_FADE_IN:
updateStarfieldFadeInState(delta_time);
break;
@@ -329,10 +329,10 @@ void TitleScene::update(float delta_time) {
void TitleScene::updateStarfieldFadeInState(float delta_time) {
temps_acumulat_ += delta_time;
const float PROGRESS = std::min(1.0F, temps_acumulat_ / DURACIO_FADE_IN);
const float PROGRESS = std::min(1.0F, temps_acumulat_ / DURATION_FADE_IN);
starfield_->setBrightness(PROGRESS * BRIGHTNESS_STARFIELD);
if (temps_acumulat_ >= DURACIO_FADE_IN) {
estat_actual_ = TitleState::STARFIELD;
if (temps_acumulat_ >= DURATION_FADE_IN) {
current_state_ = TitleState::STARFIELD;
temps_acumulat_ = 0.0F;
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
}
@@ -340,16 +340,16 @@ void TitleScene::updateStarfieldFadeInState(float delta_time) {
void TitleScene::updateStarfieldState(float delta_time) {
temps_acumulat_ += delta_time;
if (temps_acumulat_ >= DURACIO_INIT) {
estat_actual_ = TitleState::MAIN;
temps_estat_main_ = 0.0F;
animacio_activa_ = false;
factor_lerp_ = 0.0F;
if (temps_acumulat_ >= DURATION_INIT) {
current_state_ = TitleState::MAIN;
state_time_main_ = 0.0F;
animation_active_ = false;
lerp_factor_ = 0.0F;
}
}
void TitleScene::updateMainState(float delta_time) {
temps_estat_main_ += delta_time;
state_time_main_ += delta_time;
namespace S = Defaults::Title::Sequence;
namespace Sh = Defaults::Title::Ships;
@@ -364,38 +364,38 @@ void TitleScene::updateMainState(float delta_time) {
constexpr float T_SHIPS_LANDED = T_SHIPS_START + Sh::ENTRY_DURATION + Sh::P2_ENTRY_DELAY;
constexpr float T_PRESS_START_VISIBLE = T_SHIPS_LANDED + S::PRESS_START_DELAY_AFTER_SHIPS;
intro_logo_progress_ = std::clamp(temps_estat_main_ / S::LOGO_ENTRY_DURATION, 0.0F, 1.0F);
intro_logo_progress_ = std::clamp(state_time_main_ / S::LOGO_ENTRY_DURATION, 0.0F, 1.0F);
intro_jailgames_progress_ = std::clamp(
(temps_estat_main_ - T_FOOTER_START) / S::JAILGAMES_ENTRY_DURATION,
(state_time_main_ - T_FOOTER_START) / S::JAILGAMES_ENTRY_DURATION,
0.0F,
1.0F);
intro_copyright_progress_ = std::clamp(
(temps_estat_main_ - T_COPY_START) / S::COPYRIGHT_ENTRY_DURATION,
(state_time_main_ - T_COPY_START) / S::COPYRIGHT_ENTRY_DURATION,
0.0F,
1.0F);
if (!ships_intro_launched_ && temps_estat_main_ >= T_SHIPS_START &&
if (!ships_intro_launched_ && state_time_main_ >= T_SHIPS_START &&
ship_animator_ != nullptr) {
ship_animator_->setVisible(true);
ship_animator_->startEntryAnimation();
ships_intro_launched_ = true;
}
if (!press_start_visible_ && temps_estat_main_ >= T_PRESS_START_VISIBLE) {
if (!press_start_visible_ && state_time_main_ >= T_PRESS_START_VISIBLE) {
press_start_visible_ = true;
}
// L'oscil·lació suau del logo arrenca quan el logo ha aterrat. Així
// l'amplitud creix gradualment (lerp) durant la resta de la intro.
if (temps_estat_main_ < S::LOGO_ENTRY_DURATION) {
factor_lerp_ = 0.0F;
animacio_activa_ = false;
} else if (temps_estat_main_ < S::LOGO_ENTRY_DURATION + DURACIO_LERP) {
factor_lerp_ = (temps_estat_main_ - S::LOGO_ENTRY_DURATION) / DURACIO_LERP;
animacio_activa_ = true;
if (state_time_main_ < S::LOGO_ENTRY_DURATION) {
lerp_factor_ = 0.0F;
animation_active_ = false;
} else if (state_time_main_ < S::LOGO_ENTRY_DURATION + DURATION_LERP) {
lerp_factor_ = (state_time_main_ - S::LOGO_ENTRY_DURATION) / DURATION_LERP;
animation_active_ = true;
} else {
factor_lerp_ = 1.0F;
animacio_activa_ = true;
lerp_factor_ = 1.0F;
animation_active_ = true;
}
updateLogoAnimation(delta_time);
}
@@ -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_);
@@ -414,27 +414,27 @@ void TitleScene::updatePlayerJoinPhaseState(float delta_time) {
temps_acumulat_ = 0.0F;
}
if (temps_acumulat_ >= DURACIO_TRANSITION) {
estat_actual_ = TitleState::BLACK_SCREEN;
if (temps_acumulat_ >= DURATION_TRANSITION) {
current_state_ = TitleState::BLACK_SCREEN;
temps_acumulat_ = 0.0F;
}
}
void TitleScene::updateBlackScreenState(float delta_time) {
temps_acumulat_ += delta_time;
if (temps_acumulat_ >= DURACIO_BLACK_SCREEN) {
if (temps_acumulat_ >= DURATION_BLACK_SCREEN) {
context_.setNextScene(SceneType::GAME);
}
}
void TitleScene::handleSkipInput() {
if (estat_actual_ != TitleState::STARFIELD_FADE_IN && estat_actual_ != TitleState::STARFIELD) {
if (current_state_ != TitleState::STARFIELD_FADE_IN && current_state_ != TitleState::STARFIELD) {
return;
}
if (!checkSkipButtonPressed()) {
return;
}
estat_actual_ = TitleState::MAIN;
current_state_ = TitleState::MAIN;
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
// Saltar la intro coreografiada: deixar tots els elements ja in-place.
@@ -445,7 +445,7 @@ void TitleScene::handleSkipInput() {
S::LOGO_ENTRY_DURATION + S::COPYRIGHT_STAGGER + S::COPYRIGHT_ENTRY_DURATION);
constexpr float T_PRESS_START_VISIBLE = T_FOOTER_END + S::SHIPS_DELAY_AFTER_FOOTER +
Sh::ENTRY_DURATION + Sh::P2_ENTRY_DELAY + S::PRESS_START_DELAY_AFTER_SHIPS;
temps_estat_main_ = T_PRESS_START_VISIBLE;
state_time_main_ = T_PRESS_START_VISIBLE;
intro_logo_progress_ = 1.0F;
intro_jailgames_progress_ = 1.0F;
intro_copyright_progress_ = 1.0F;
@@ -458,7 +458,7 @@ void TitleScene::handleSkipInput() {
}
void TitleScene::handleStartInput() {
if (estat_actual_ != TitleState::MAIN) {
if (current_state_ != TitleState::MAIN) {
return;
}
// No acceptar START fins que la intro coreografiada haja conclòs i el
@@ -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;
@@ -479,7 +479,7 @@ void TitleScene::handleStartInput() {
}
context_.setMatchConfig(match_config_);
estat_actual_ = TitleState::PLAYER_JOIN_PHASE;
current_state_ = TitleState::PLAYER_JOIN_PHASE;
temps_acumulat_ = 0.0F;
triggerExitForJoinedPlayers(P1_ABANS, P2_ABANS, "");
@@ -492,55 +492,55 @@ 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";
}
}
void TitleScene::updateLogoAnimation(float delta_time) {
if (!animacio_activa_) {
if (!animation_active_) {
return;
}
temps_animacio_ += delta_time * factor_lerp_;
animation_time_ += delta_time * lerp_factor_;
const float TWO_PI = 2.0F * Defaults::Math::PI;
const float OFFSET_X = ORBIT_AMPLITUDE_X * std::sin(TWO_PI * ORBIT_FREQUENCY_X * temps_animacio_);
const float OFFSET_Y = ORBIT_AMPLITUDE_Y * std::sin((TWO_PI * ORBIT_FREQUENCY_Y * temps_animacio_) + ORBIT_PHASE_OFFSET);
const float OFFSET_X = ORBIT_AMPLITUDE_X * std::sin(TWO_PI * ORBIT_FREQUENCY_X * animation_time_);
const float OFFSET_Y = ORBIT_AMPLITUDE_Y * std::sin((TWO_PI * ORBIT_FREQUENCY_Y * animation_time_) + ORBIT_PHASE_OFFSET);
for (std::size_t i = 0; i < lletres_orni_.size(); ++i) {
lletres_orni_[i].position.x = posicions_originals_orni_[i].x + std::round(OFFSET_X);
lletres_orni_[i].position.y = posicions_originals_orni_[i].y + std::round(OFFSET_Y);
for (std::size_t i = 0; i < letters_orni_.size(); ++i) {
letters_orni_[i].position.x = original_positions_orni_[i].x + std::round(OFFSET_X);
letters_orni_[i].position.y = original_positions_orni_[i].y + std::round(OFFSET_Y);
}
for (std::size_t i = 0; i < lletres_attack_.size(); ++i) {
lletres_attack_[i].position.x = posicions_originals_attack_[i].x + std::round(OFFSET_X);
lletres_attack_[i].position.y = posicions_originals_attack_[i].y + std::round(OFFSET_Y);
for (std::size_t i = 0; i < letters_attack_.size(); ++i) {
letters_attack_[i].position.x = original_positions_attack_[i].x + std::round(OFFSET_X);
letters_attack_[i].position.y = original_positions_attack_[i].y + std::round(OFFSET_Y);
}
}
void TitleScene::draw() {
if (starfield_ && estat_actual_ != TitleState::BLACK_SCREEN) {
if (starfield_ && current_state_ != TitleState::BLACK_SCREEN) {
starfield_->draw();
}
if (ship_animator_ &&
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
estat_actual_ == TitleState::STARFIELD ||
estat_actual_ == TitleState::MAIN ||
estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) {
(current_state_ == TitleState::STARFIELD_FADE_IN ||
current_state_ == TitleState::STARFIELD ||
current_state_ == TitleState::MAIN ||
current_state_ == TitleState::PLAYER_JOIN_PHASE)) {
ship_animator_->draw();
}
drawFlashes();
if (estat_actual_ == TitleState::STARFIELD_FADE_IN || estat_actual_ == TitleState::STARFIELD) {
if (current_state_ == TitleState::STARFIELD_FADE_IN || current_state_ == TitleState::STARFIELD) {
return;
}
if (estat_actual_ != TitleState::MAIN && estat_actual_ != TitleState::PLAYER_JOIN_PHASE) {
if (current_state_ != TitleState::MAIN && current_state_ != TitleState::PLAYER_JOIN_PHASE) {
return;
}
@@ -552,52 +552,52 @@ void TitleScene::draw() {
const float SCREEN_CENTRE_Y = Defaults::Game::HEIGHT / 2.0F;
const float LOGO_RENDER_SCALE = Defaults::Title::Layout::LOGO_SCALE * LOGO_S;
if (animacio_activa_) {
float temps_shadow = std::max(0.0F, temps_animacio_ - SHADOW_DELAY);
if (animation_active_) {
float temps_shadow = std::max(0.0F, animation_time_ - SHADOW_DELAY);
const float TWO_PI = 2.0F * Defaults::Math::PI;
const float SHADOW_OX = (ORBIT_AMPLITUDE_X * std::sin(TWO_PI * ORBIT_FREQUENCY_X * temps_shadow)) + SHADOW_OFFSET_X;
const float SHADOW_OY = (ORBIT_AMPLITUDE_Y * std::sin((TWO_PI * ORBIT_FREQUENCY_Y * temps_shadow) + ORBIT_PHASE_OFFSET)) + SHADOW_OFFSET_Y;
for (std::size_t i = 0; i < lletres_orni_.size(); ++i) {
const float BASE_X = posicions_originals_orni_[i].x + std::round(SHADOW_OX);
const float BASE_Y = posicions_originals_orni_[i].y + std::round(SHADOW_OY);
for (std::size_t i = 0; i < letters_orni_.size(); ++i) {
const float BASE_X = original_positions_orni_[i].x + std::round(SHADOW_OX);
const float BASE_Y = original_positions_orni_[i].y + std::round(SHADOW_OY);
const Vec2 POS_SHADOW{
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (LOGO_S * (BASE_Y - SCREEN_CENTRE_Y)),
};
Rendering::renderShape(sdl_.getRenderer(), lletres_orni_[i].shape, POS_SHADOW, 0.0F, LOGO_RENDER_SCALE, 1.0F, SHADOW_BRIGHTNESS, Defaults::Title::Colors::LOGO_SHADOW);
Rendering::renderShape(sdl_.getRenderer(), letters_orni_[i].shape, POS_SHADOW, 0.0F, LOGO_RENDER_SCALE, 1.0F, SHADOW_BRIGHTNESS, Defaults::Title::Colors::LOGO_SHADOW);
}
for (std::size_t i = 0; i < lletres_attack_.size(); ++i) {
const float BASE_X = posicions_originals_attack_[i].x + std::round(SHADOW_OX);
const float BASE_Y = posicions_originals_attack_[i].y + std::round(SHADOW_OY);
for (std::size_t i = 0; i < letters_attack_.size(); ++i) {
const float BASE_X = original_positions_attack_[i].x + std::round(SHADOW_OX);
const float BASE_Y = original_positions_attack_[i].y + std::round(SHADOW_OY);
const Vec2 POS_SHADOW{
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (LOGO_S * (BASE_Y - SCREEN_CENTRE_Y)),
};
Rendering::renderShape(sdl_.getRenderer(), lletres_attack_[i].shape, POS_SHADOW, 0.0F, LOGO_RENDER_SCALE, 1.0F, SHADOW_BRIGHTNESS, Defaults::Title::Colors::LOGO_SHADOW);
Rendering::renderShape(sdl_.getRenderer(), letters_attack_[i].shape, POS_SHADOW, 0.0F, LOGO_RENDER_SCALE, 1.0F, SHADOW_BRIGHTNESS, Defaults::Title::Colors::LOGO_SHADOW);
}
}
for (const auto& lletra : lletres_orni_) {
for (const auto& letter : letters_orni_) {
const Vec2 POS{
.x = SCREEN_CENTRE_X + (LOGO_S * (lletra.position.x - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (LOGO_S * (lletra.position.y - SCREEN_CENTRE_Y)),
.x = SCREEN_CENTRE_X + (LOGO_S * (letter.position.x - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (LOGO_S * (letter.position.y - SCREEN_CENTRE_Y)),
};
Rendering::renderShape(sdl_.getRenderer(), lletra.shape, POS, 0.0F, LOGO_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::LOGO_MAIN);
Rendering::renderShape(sdl_.getRenderer(), letter.shape, POS, 0.0F, LOGO_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::LOGO_MAIN);
}
for (const auto& lletra : lletres_attack_) {
for (const auto& letter : letters_attack_) {
const Vec2 POS{
.x = SCREEN_CENTRE_X + (LOGO_S * (lletra.position.x - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (LOGO_S * (lletra.position.y - SCREEN_CENTRE_Y)),
.x = SCREEN_CENTRE_X + (LOGO_S * (letter.position.x - SCREEN_CENTRE_X)),
.y = SCREEN_CENTRE_Y + (LOGO_S * (letter.position.y - SCREEN_CENTRE_Y)),
};
Rendering::renderShape(sdl_.getRenderer(), lletra.shape, POS, 0.0F, LOGO_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::LOGO_MAIN);
Rendering::renderShape(sdl_.getRenderer(), letter.shape, POS, 0.0F, LOGO_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::LOGO_MAIN);
}
const float SPACING = Defaults::Title::Layout::TEXT_SPACING;
if (press_start_visible_) {
bool mostrar_text = true;
if (estat_actual_ == TitleState::PLAYER_JOIN_PHASE) {
if (current_state_ == TitleState::PLAYER_JOIN_PHASE) {
const float FASE = temps_acumulat_ * BLINK_FREQUENCY * 2.0F * std::numbers::pi_v<float>;
mostrar_text = (std::sin(FASE) > 0.0F);
}
@@ -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;
}
}
+21 -21
View File
@@ -48,12 +48,12 @@ class TitleScene final : public Scene {
BLACK_SCREEN,
};
struct LetraLogo {
struct LogoLetter {
std::shared_ptr<Graphics::Shape> shape;
Vec2 position;
float ancho;
float altura;
float offset_centre;
float width;
float height;
float center_offset;
};
SDLManager& sdl_;
@@ -77,22 +77,22 @@ class TitleScene final : public Scene {
void triggerFlash(Vec2 pos);
void updateFlashes(float delta_time);
void drawFlashes();
TitleState estat_actual_{TitleState::STARFIELD_FADE_IN};
TitleState current_state_{TitleState::STARFIELD_FADE_IN};
float temps_acumulat_{0.0F};
std::vector<LetraLogo> lletres_orni_;
std::vector<LetraLogo> lletres_attack_;
float y_attack_dinamica_{0.0F};
std::vector<LogoLetter> letters_orni_;
std::vector<LogoLetter> letters_attack_;
float dynamic_attack_y_{0.0F};
std::vector<LetraLogo> lletres_jailgames_;
std::vector<LogoLetter> letters_jailgames_;
float temps_animacio_{0.0F};
std::vector<Vec2> posicions_originals_orni_;
std::vector<Vec2> posicions_originals_attack_;
float animation_time_{0.0F};
std::vector<Vec2> original_positions_orni_;
std::vector<Vec2> original_positions_attack_;
float temps_estat_main_{0.0F};
bool animacio_activa_{false};
float factor_lerp_{0.0F};
float state_time_main_{0.0F};
bool animation_active_{false};
float lerp_factor_{0.0F};
// Progresos de la intro coreografiada al state MAIN.
float intro_logo_progress_{0.0F};
@@ -102,12 +102,12 @@ class TitleScene final : public Scene {
bool ships_intro_launched_{false};
static constexpr float BRIGHTNESS_STARFIELD = 1.2F;
static constexpr float DURACIO_FADE_IN = 3.0F;
static constexpr float DURACIO_INIT = 4.0F;
static constexpr float DURACIO_TRANSITION = 2.5F;
static constexpr float ESPAI_ENTRE_LLETRES = 10.0F;
static constexpr float DURATION_FADE_IN = 3.0F;
static constexpr float DURATION_INIT = 4.0F;
static constexpr float DURATION_TRANSITION = 2.5F;
static constexpr float LETTER_SPACING = 10.0F;
static constexpr float BLINK_FREQUENCY = 3.0F;
static constexpr float DURACIO_BLACK_SCREEN = 2.0F;
static constexpr float DURATION_BLACK_SCREEN = 2.0F;
static constexpr int MUSIC_FADE = 1500;
static constexpr float ORBIT_AMPLITUDE_X = 4.0F;
@@ -121,7 +121,7 @@ class TitleScene final : public Scene {
static constexpr float SHADOW_OFFSET_X = 2.0F;
static constexpr float SHADOW_OFFSET_Y = 2.0F;
static constexpr float DURACIO_LERP = 2.0F;
static constexpr float DURATION_LERP = 2.0F;
// Càmera 3D: FOV vertical en radians.
static constexpr float CAMERA_FOV_Y_RAD = 1.0472F; // 60°
+28 -28
View File
@@ -16,13 +16,13 @@
namespace StageSystem {
SpawnController::SpawnController() = default;
SpawnController::SpawnController() = default;
void SpawnController::configure(const StageConfig* config) {
void SpawnController::configure(const StageConfig* config) {
config_ = config;
}
}
void SpawnController::start() {
void SpawnController::start() {
if (config_ == nullptr) {
std::cerr << "[SpawnController] Error: config_ es null" << '\n';
return;
@@ -33,15 +33,15 @@ void SpawnController::start() {
std::cout << "[SpawnController] Stage " << static_cast<int>(config_->stage_id)
<< ": generats " << spawn_queue_.size() << " spawn events" << '\n';
}
}
void SpawnController::reset() {
void SpawnController::reset() {
spawn_queue_.clear();
temps_transcorregut_ = 0.0F;
index_spawn_actual_ = 0;
}
}
void SpawnController::update(float delta_time, std::array<Enemy, 15>& orni_array, bool pausar) {
void SpawnController::update(float delta_time, std::array<Enemy, 15>& orni_array, bool pausar) {
if ((config_ == nullptr) || spawn_queue_.empty()) {
return;
}
@@ -80,20 +80,20 @@ void SpawnController::update(float delta_time, std::array<Enemy, 15>& orni_array
break;
}
}
}
}
auto SpawnController::allEnemiesSpawned() const -> bool {
auto SpawnController::allEnemiesSpawned() const -> bool {
return index_spawn_actual_ >= spawn_queue_.size();
}
}
auto SpawnController::allEnemiesDestroyed(const std::array<Enemy, 15>& orni_array) const -> bool {
auto SpawnController::allEnemiesDestroyed(const std::array<Enemy, 15>& orni_array) const -> bool {
if (!allEnemiesSpawned()) {
return false;
}
return std::ranges::all_of(orni_array, [](const Enemy& enemy) { return !enemy.isActive(); });
}
}
auto SpawnController::getAliveEnemyCount(const std::array<Enemy, 15>& orni_array) -> uint8_t {
auto SpawnController::getAliveEnemyCount(const std::array<Enemy, 15>& orni_array) -> uint8_t {
uint8_t count = 0;
for (const auto& enemy : orni_array) {
if (enemy.isActive()) {
@@ -101,13 +101,13 @@ auto SpawnController::getAliveEnemyCount(const std::array<Enemy, 15>& orni_array
}
}
return count;
}
}
auto SpawnController::countSpawnedEnemies() const -> uint8_t {
auto SpawnController::countSpawnedEnemies() const -> uint8_t {
return static_cast<uint8_t>(index_spawn_actual_);
}
}
void SpawnController::generateSpawnEvents() {
void SpawnController::generateSpawnEvents() {
if (config_ == nullptr) {
return;
}
@@ -120,9 +120,9 @@ void SpawnController::generateSpawnEvents() {
spawn_queue_.push_back({spawn_time, type, false});
}
}
}
auto SpawnController::selectRandomType() const -> EnemyType {
auto SpawnController::selectRandomType() const -> EnemyType {
if (config_ == nullptr) {
return EnemyType::PENTAGON;
}
@@ -134,20 +134,20 @@ auto SpawnController::selectRandomType() const -> EnemyType {
return EnemyType::PENTAGON;
}
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) {
return EnemyType::QUADRAT;
return EnemyType::SQUARE;
}
return EnemyType::PINWHEEL;
}
return EnemyType::MOLINILLO;
}
void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) {
void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) {
// Initialize enemy (with safe spawn if ship_pos provided)
enemy.init(type, ship_pos);
// Apply difficulty multipliers
applyMultipliers(enemy);
}
}
void SpawnController::applyMultipliers(Enemy& enemy) const {
void SpawnController::applyMultipliers(Enemy& enemy) const {
if (config_ == nullptr) {
return;
}
@@ -160,8 +160,8 @@ void SpawnController::applyMultipliers(Enemy& enemy) const {
float base_rot = enemy.getBaseRotation();
enemy.setRotation(base_rot * config_->multiplicadors.rotation);
// Apply tracking strength (only affects QUADRAT)
// Apply tracking strength (only affects SQUARE)
enemy.setTrackingStrength(config_->multiplicadors.tracking_strength);
}
}
} // namespace StageSystem
+1 -1
View File
@@ -36,7 +36,7 @@ namespace StageSystem {
struct MultiplicadorsDificultat {
float velocity; // 0.5-2.0 típic
float rotation; // 0.5-2.0 típic
float tracking_strength; // 0.0-1.5 (aplicat a Cuadrado)
float tracking_strength; // 0.0-1.5 (aplicat a Square)
};
// Metadades del file YAML
+18 -18
View File
@@ -20,10 +20,10 @@ namespace Systems::Collision {
switch (type) {
case EnemyType::PENTAGON:
return Defaults::Enemies::Scoring::PENTAGON_SCORE;
case EnemyType::QUADRAT:
return Defaults::Enemies::Scoring::QUADRAT_SCORE;
case EnemyType::MOLINILLO:
return Defaults::Enemies::Scoring::MOLINILLO_SCORE;
case EnemyType::SQUARE:
return Defaults::Enemies::Scoring::SQUARE_SCORE;
case EnemyType::PINWHEEL:
return Defaults::Enemies::Scoring::PINWHEEL_SCORE;
}
return 0;
}
@@ -32,10 +32,10 @@ namespace Systems::Collision {
switch (type) {
case EnemyType::PENTAGON:
return Defaults::Palette::PENTAGON;
case EnemyType::QUADRAT:
return Defaults::Palette::QUADRAT;
case EnemyType::MOLINILLO:
return Defaults::Palette::MOLINILLO;
case EnemyType::SQUARE:
return Defaults::Palette::SQUARE;
case EnemyType::PINWHEEL:
return Defaults::Palette::PINWHEEL;
}
return SDL_Color{};
}
@@ -58,16 +58,16 @@ namespace Systems::Collision {
}
ctx.floating_score_manager.crear(POINTS, ENEMY_POS);
enemy.destruir();
enemy.destroy();
constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (explosión suave)
constexpr float SPEED_EXPLOSIO = 80.0F; // px/s (explosión suave)
const Vec2 INHERITED_VEL = ENEMY_VEL * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
ctx.debris_manager.explode(
SHAPE,
ENEMY_POS,
0.0F, // angle (rotación interna del enemy)
1.0F, // escala
VELOCITAT_EXPLOSIO,
SPEED_EXPLOSIO,
BRIGHTNESS,
INHERITED_VEL,
0.0F, // sense herència angular: evita que els 5 trossos curvin en bloc
@@ -141,7 +141,7 @@ namespace Systems::Collision {
explodeNow(ctx, enemy, SHOOTER);
} else {
// Primer impacto → entra en estado herido (explosión diferida).
enemy.herir(SHOOTER);
enemy.hurt(SHOOTER);
}
breakBullet(ctx.debris_manager, bullet);
@@ -182,9 +182,9 @@ namespace Systems::Collision {
}
// El sano queda herido, propagando el shooter original.
if (A_WOUNDED) {
b.herir(a.getLastHitBy());
b.hurt(a.getLastHitBy());
} else {
a.herir(b.getLastHitBy());
a.hurt(b.getLastHitBy());
}
}
}
@@ -230,7 +230,7 @@ namespace Systems::Collision {
} else {
// Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
// l'enemic no rep dany per decisió de disseny).
ctx.ships[i].herir();
ctx.ships[i].hurt();
}
}
ctx.ships[i].setTouchingEnemyPrevFrame(TOUCHING_NOW);
@@ -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;
}
@@ -297,7 +297,7 @@ namespace Systems::Collision {
}
void desactivateOutOfBoundsBullets(
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BALES) * 2>& bullets,
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS) * 2>& bullets,
Effects::DebrisManager& debris_manager) {
float min_x;
float max_x;
+2 -2
View File
@@ -32,7 +32,7 @@ namespace Systems::Collision {
struct Context {
std::array<Ship, 2>& ships;
std::array<Enemy, Defaults::Entities::MAX_ORNIS>& enemies;
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BALES) * 2>& bullets;
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS) * 2>& bullets;
std::array<float, 2>& hit_timer_per_player;
std::array<int, 2>& score_per_player;
std::array<int, 2>& lives_per_player;
@@ -74,7 +74,7 @@ namespace Systems::Collision {
// (8 fragments de l'octàgon) i el so HIT. Cal cridar-la després de detectAll()
// perquè una bala que el mateix frame xoca i alhora surt es comptabilitzi com a impacte.
void desactivateOutOfBoundsBullets(
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BALES) * 2>& bullets,
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS) * 2>& bullets,
Effects::DebrisManager& debris_manager);
} // namespace Systems::Collision
+13 -13
View File
@@ -11,34 +11,34 @@
#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) {
// 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) {
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;
ctx.match_config.player1_active = true;
} else {
ctx.match_config.jugador2_actiu = true;
ctx.match_config.player2_active = 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) {
void update(Context& ctx, float delta_time) {
ctx.tick_timer -= delta_time;
if (ctx.tick_timer > 0.0F) {
return;
@@ -52,9 +52,9 @@ void update(Context& ctx, float delta_time) {
if (ctx.state == GameOverState::CONTINUE) {
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
}
}
}
void processInput(Context& ctx) {
void processInput(Context& ctx) {
auto* input = Input::get();
const bool P1_START = input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
@@ -104,6 +104,6 @@ void processInput(Context& ctx) {
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};
+18 -18
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,28 +21,28 @@
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,
// 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,
// 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);