Merge branch 'refactor/english-identifiers': identificadors valencians/castellans a anglès
This commit is contained in:
@@ -9,8 +9,8 @@ namespace Defaults::FX::Glow {
|
|||||||
|
|
||||||
// Neon glow per outline gruixut, aplicat automàticament per renderShape.
|
// 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
|
// 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
|
// per scale), de manera que un pentàgon (radius 20) té halo gros i una bala
|
||||||
// (radi 3) té halo subtil. El core (últim pass) usa el gruix de línia
|
// (radius 3) té halo subtil. El core (últim pass) usa el gruix de línia
|
||||||
// global (1.5px) — no escala amb la shape.
|
// global (1.5px) — no escala amb la shape.
|
||||||
//
|
//
|
||||||
// Cap superior: si la shape és molt gran (logos del títol, intro), el
|
// Cap superior: si la shape és molt gran (logos del títol, intro), el
|
||||||
|
|||||||
@@ -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
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -17,57 +17,57 @@ namespace Defaults::Enemies {
|
|||||||
|
|
||||||
// Pentagon (esquivador - zigzag evasion)
|
// Pentagon (esquivador - zigzag evasion)
|
||||||
namespace Pentagon {
|
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 MASS = 5.0F; // Masa estándar
|
||||||
constexpr float CANVI_ANGLE_PROB = 0.20F; // 20% per wall hit (frequent zigzag)
|
constexpr float ANGLE_CHANGE_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_MAX = 1.0F; // Max random angle change (rad)
|
||||||
constexpr float ZIGZAG_PROB_PER_SECOND = 0.8F; // Probabilidad de zigzag por segundo
|
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 ROTATION_DELTA_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_MAX = 3.75F; // Max visual rotation (rad/s) [+50%]
|
||||||
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
|
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
|
||||||
} // namespace Pentagon
|
} // namespace Pentagon
|
||||||
|
|
||||||
// Cuadrado (perseguidor - tracks player)
|
// Square (perseguidor - tracks player)
|
||||||
namespace Cuadrado {
|
namespace Square {
|
||||||
constexpr float VELOCITAT = 40.0F; // px/s (medium speed)
|
constexpr float SPEED = 40.0F; // px/s (medium speed)
|
||||||
constexpr float MASS = 8.0F; // Más pesado, "tanque"
|
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_STRENGTH = 0.5F; // Interpolation toward player (0.0-1.0)
|
||||||
constexpr float TRACKING_INTERVAL = 1.0F; // Seconds between angle updates
|
constexpr float TRACKING_INTERVAL = 1.0F; // Seconds between angle updates
|
||||||
constexpr float DROTACIO_MIN = 0.3F; // Slow rotation [+50%]
|
constexpr float ROTATION_DELTA_MIN = 0.3F; // Slow rotation [+50%]
|
||||||
constexpr float DROTACIO_MAX = 1.5F; // [+50%]
|
constexpr float ROTATION_DELTA_MAX = 1.5F; // [+50%]
|
||||||
constexpr const char* SHAPE_FILE = "enemy_square.shp";
|
constexpr const char* SHAPE_FILE = "enemy_square.shp";
|
||||||
} // namespace Cuadrado
|
} // namespace Square
|
||||||
|
|
||||||
// Molinillo (agressiu - fast straight lines, proximity spin-up)
|
// Molinillo (agressiu - fast straight lines, proximity spin-up)
|
||||||
namespace Molinillo {
|
namespace Pinwheel {
|
||||||
constexpr float VELOCITAT = 50.0F; // px/s (fastest)
|
constexpr float SPEED = 50.0F; // px/s (fastest)
|
||||||
constexpr float MASS = 4.0F; // Más liviano, ágil
|
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 ANGLE_CHANGE_PROB = 0.05F; // 5% per wall hit (rare direction change)
|
||||||
constexpr float CANVI_ANGLE_MAX = 0.3F; // Small angle adjustments
|
constexpr float ANGLE_CHANGE_MAX = 0.3F; // Small angle adjustments
|
||||||
constexpr float DROTACIO_MIN = 3.0F; // Base rotation (rad/s) [+50%]
|
constexpr float ROTATION_DELTA_MIN = 3.0F; // Base rotation (rad/s) [+50%]
|
||||||
constexpr float DROTACIO_MAX = 6.0F; // [+50%]
|
constexpr float ROTATION_DELTA_MAX = 6.0F; // [+50%]
|
||||||
constexpr float DROTACIO_PROXIMITY_MULTIPLIER = 3.0F; // Spin-up multiplier when near ship
|
constexpr float ROTATION_DELTA_PROXIMITY_MULTIPLIER = 3.0F; // Spin-up multiplier when near ship
|
||||||
constexpr float PROXIMITY_DISTANCE = 100.0F; // Distance threshold (px)
|
constexpr float PROXIMITY_DISTANCE = 100.0F; // Distance threshold (px)
|
||||||
constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp";
|
constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp";
|
||||||
} // namespace Molinillo
|
} // namespace Pinwheel
|
||||||
|
|
||||||
// Animation parameters (shared)
|
// Animation parameters (shared)
|
||||||
namespace Animation {
|
namespace Animation {
|
||||||
// Palpitation
|
// Palpitation
|
||||||
constexpr float PALPITACIO_TRIGGER_PROB = 0.01F; // 1% chance per second
|
constexpr float PULSE_TRIGGER_PROB = 0.01F; // 1% chance per second
|
||||||
constexpr float PALPITACIO_DURACIO_MIN = 1.0F; // Min duration (seconds)
|
constexpr float PULSE_DURATION_MIN = 1.0F; // Min duration (seconds)
|
||||||
constexpr float PALPITACIO_DURACIO_MAX = 3.0F; // Max duration (seconds)
|
constexpr float PULSE_DURATION_MAX = 3.0F; // Max duration (seconds)
|
||||||
constexpr float PALPITACIO_AMPLITUD_MIN = 0.08F; // Min scale variation
|
constexpr float PULSE_AMPLITUD_MIN = 0.08F; // Min scale variation
|
||||||
constexpr float PALPITACIO_AMPLITUD_MAX = 0.20F; // Max scale variation
|
constexpr float PULSE_AMPLITUD_MAX = 0.20F; // Max scale variation
|
||||||
constexpr float PALPITACIO_FREQ_MIN = 1.5F; // Min frequency (Hz)
|
constexpr float PULSE_FREQ_MIN = 1.5F; // Min frequency (Hz)
|
||||||
constexpr float PALPITACIO_FREQ_MAX = 3.0F; // Max frequency (Hz)
|
constexpr float PULSE_FREQ_MAX = 3.0F; // Max frequency (Hz)
|
||||||
|
|
||||||
// Rotation acceleration
|
// Rotation acceleration
|
||||||
constexpr float ROTACIO_ACCEL_TRIGGER_PROB = 0.02F; // 2% chance per second [4x more frequent]
|
constexpr float ROTATION_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 ROTATION_ACCEL_DURATION_MIN = 3.0F; // Min transition time
|
||||||
constexpr float ROTACIO_ACCEL_DURACIO_MAX = 8.0F; // Max transition time
|
constexpr float ROTATION_ACCEL_DURATION_MAX = 8.0F; // Max transition time
|
||||||
constexpr float ROTACIO_ACCEL_MULTIPLIER_MIN = 0.3F; // Min speed multiplier [more dramatic]
|
constexpr float ROTATION_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_MULTIPLIER_MAX = 4.0F; // Max speed multiplier [more dramatic]
|
||||||
} // namespace Animation
|
} // namespace Animation
|
||||||
|
|
||||||
// Wounded state (entre primer impacto y explosión)
|
// Wounded state (entre primer impacto y explosión)
|
||||||
@@ -93,9 +93,9 @@ namespace Defaults::Enemies {
|
|||||||
|
|
||||||
// Scoring system (puntuación per type de enemy)
|
// Scoring system (puntuación per type de enemy)
|
||||||
namespace Scoring {
|
namespace Scoring {
|
||||||
constexpr int PENTAGON_SCORE = 100; // Pentágono (esquivador, 35 px/s)
|
constexpr int PENTAGON_SCORE = 100; // Pentágono (esquivador, 35 px/s)
|
||||||
constexpr int QUADRAT_SCORE = 150; // Cuadrado (perseguidor, 40 px/s)
|
constexpr int SQUARE_SCORE = 150; // Square (perseguidor, 40 px/s)
|
||||||
constexpr int MOLINILLO_SCORE = 200; // Molinillo (agressiu, 50 px/s)
|
constexpr int PINWHEEL_SCORE = 200; // Molinillo (agressiu, 50 px/s)
|
||||||
} // namespace Scoring
|
} // namespace Scoring
|
||||||
|
|
||||||
} // namespace Defaults::Enemies
|
} // namespace Defaults::Enemies
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
namespace Defaults::Entities {
|
namespace Defaults::Entities {
|
||||||
|
|
||||||
constexpr int MAX_ORNIS = 15;
|
constexpr int MAX_ORNIS = 15;
|
||||||
constexpr int MAX_BALES = 50;
|
constexpr int MAX_BULLETS = 50;
|
||||||
|
|
||||||
constexpr float SHIP_RADIUS = 12.0F;
|
constexpr float SHIP_RADIUS = 12.0F;
|
||||||
constexpr float ENEMY_RADIUS = 20.0F;
|
constexpr float ENEMY_RADIUS = 20.0F;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace Defaults::Game {
|
|||||||
constexpr float INIT_HUD_SHIP2_RATIO_INIT = 0.20F;
|
constexpr float INIT_HUD_SHIP2_RATIO_INIT = 0.20F;
|
||||||
constexpr float INIT_HUD_SHIP2_RATIO_END = 1.0F;
|
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
|
constexpr float INIT_HUD_SHIP_START_Y_RATIO = 0.75F; // 75% desde el top de PLAYAREA
|
||||||
|
|
||||||
// Spawn positions (distribución horizontal para 2 jugadores)
|
// Spawn positions (distribución horizontal para 2 jugadores)
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ namespace Defaults::Palette {
|
|||||||
// brillantor perceptual sota el bloom (sense alterar la identitat de color).
|
// brillantor perceptual sota el bloom (sense alterar la identitat de color).
|
||||||
// El canal dominant es manté a 255 a cada color per maximitzar la saturació
|
// El canal dominant es manté a 255 a cada color per maximitzar la saturació
|
||||||
// visible quan el halo s'expandeix.
|
// visible quan el halo s'expandeix.
|
||||||
constexpr SDL_Color SHIP = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanco neutro
|
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 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 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 SQUARE = {.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 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
|
constexpr SDL_Color WOUNDED = {.r = 255, .g = 220, .b = 60, .a = 255}; // Dorado: enemigo herido
|
||||||
|
|
||||||
} // namespace Defaults::Palette
|
} // namespace Defaults::Palette
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ namespace Defaults::Physics {
|
|||||||
|
|
||||||
// Explosions (debris physics)
|
// Explosions (debris physics)
|
||||||
namespace Debris {
|
namespace Debris {
|
||||||
constexpr float VELOCITAT_BASE = 80.0F; // Velocidad inicial (px/s)
|
constexpr float SPEED_BASE = 80.0F; // Velocidad inicial (px/s)
|
||||||
constexpr float VARIACIO_VELOCITAT = 40.0F; // ±variació aleatòria (px/s)
|
constexpr float VARIACIO_SPEED = 40.0F; // ±variació aleatòria (px/s)
|
||||||
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (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 ROTATION_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_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 = 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 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)
|
constexpr float SHRINK_RATE = 1.0F; // Reducció de mida (1.0 = encoge a 0 al final del min_lifetime)
|
||||||
|
|
||||||
// Política de mort: passat el min_lifetime, el fragment mor quan la
|
// Política de mort: passat el min_lifetime, el fragment mor quan la
|
||||||
// seva velocity cau per sota d'aquest llindar. Així els fragments
|
// seva velocity cau per sota d'aquest llindar. Així els fragments
|
||||||
@@ -48,9 +48,9 @@ namespace Defaults::Physics {
|
|||||||
constexpr float RESTITUTION_BOUNDS = 0.7F;
|
constexpr float RESTITUTION_BOUNDS = 0.7F;
|
||||||
|
|
||||||
// Herència de velocity angular (trayectorias curvas)
|
// Herència de velocity angular (trayectorias curvas)
|
||||||
constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat
|
constexpr float INHERITANCE_FACTOR_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_MAX = 1.0F; // Màxim 100% del drotacio heredat
|
||||||
constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²)
|
constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²)
|
||||||
|
|
||||||
// Velocity heredada de la nau a l'explosió (80% del feel original).
|
// Velocity heredada de la nau a l'explosió (80% del feel original).
|
||||||
constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F;
|
constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F;
|
||||||
@@ -68,7 +68,7 @@ namespace Defaults::Physics {
|
|||||||
// Angular velocity sin for trajectory inheritance
|
// Angular velocity sin for trajectory inheritance
|
||||||
// Excess above this threshold is converted to tangential linear velocity
|
// Excess above this threshold is converted to tangential linear velocity
|
||||||
// Prevents "vortex trap" problem with high-rotation enemies
|
// 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 Debris
|
||||||
|
|
||||||
} // namespace Defaults::Physics
|
} // namespace Defaults::Physics
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace Defaults::Title {
|
|||||||
constexpr float FLOATING_SCALE = 1.0F * SHIP_BASE_SCALE; // Flotante: scale base
|
constexpr float FLOATING_SCALE = 1.0F * SHIP_BASE_SCALE; // Flotante: scale base
|
||||||
|
|
||||||
// Offset de entrada (ajustat automáticoament a l'scale)
|
// 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;
|
constexpr float ENTRY_OFFSET = (SHIP_MAX_RADIUS * ENTRY_SCALE_START) + ENTRY_OFFSET_MARGIN;
|
||||||
|
|
||||||
// Vec2 de fuga (centro para l'animación de salida)
|
// Vec2 de fuga (centro para l'animación de salida)
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ namespace Graphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Border::bumpAt(Vec2 contact_point, float strength) {
|
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 = {
|
const std::array<float, SIDE_COUNT> DISTANCES = {
|
||||||
/* TOP */ std::abs(contact_point.y - zona.y),
|
/* TOP */ std::abs(contact_point.y - zone.y),
|
||||||
/* RIGHT */ std::abs((zona.x + zona.w) - contact_point.x),
|
/* RIGHT */ std::abs((zone.x + zone.w) - contact_point.x),
|
||||||
/* BOTTOM */ std::abs((zona.y + zona.h) - contact_point.y),
|
/* BOTTOM */ std::abs((zone.y + zone.h) - contact_point.y),
|
||||||
/* LEFT */ std::abs(contact_point.x - zona.x)};
|
/* LEFT */ std::abs(contact_point.x - zone.x)};
|
||||||
|
|
||||||
int closest_idx = 0;
|
int closest_idx = 0;
|
||||||
float closest_dist = DISTANCES[0];
|
float closest_dist = DISTANCES[0];
|
||||||
@@ -71,11 +71,11 @@ namespace Graphics {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void Border::draw() const {
|
void Border::draw() const {
|
||||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
|
||||||
const int X1 = static_cast<int>(zona.x);
|
const int X1 = static_cast<int>(zone.x);
|
||||||
const int Y1 = static_cast<int>(zona.y);
|
const int Y1 = static_cast<int>(zone.y);
|
||||||
const int X2 = static_cast<int>(zona.x + zona.w);
|
const int X2 = static_cast<int>(zone.x + zone.w);
|
||||||
const int Y2 = static_cast<int>(zona.y + zona.h);
|
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_TOP = static_cast<int>(sides_[SIDE_TOP].displacement_px);
|
||||||
const int OFF_RIGHT = static_cast<int>(sides_[SIDE_RIGHT].displacement_px);
|
const int OFF_RIGHT = static_cast<int>(sides_[SIDE_RIGHT].displacement_px);
|
||||||
|
|||||||
@@ -141,9 +141,9 @@ namespace Graphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Playfield::buildLines() {
|
void Playfield::buildLines() {
|
||||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
|
||||||
const float CELL_W = zona.w / static_cast<float>(Defaults::Playfield::COLUMNS);
|
const float CELL_W = zone.w / static_cast<float>(Defaults::Playfield::COLUMNS);
|
||||||
const float CELL_H = zona.h / static_cast<float>(Defaults::Playfield::ROWS);
|
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_W = CELL_W / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
|
||||||
const float SUB_H = CELL_H / 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;
|
const int SUB_VERTS = Defaults::Playfield::COLUMNS * Defaults::Playfield::SUBDIVISIONS;
|
||||||
@@ -154,14 +154,14 @@ namespace Graphics {
|
|||||||
|
|
||||||
// Verticals: posicions i ∈ [1, SUB_VERTS-1].
|
// Verticals: posicions i ∈ [1, SUB_VERTS-1].
|
||||||
for (int i = 1; i < SUB_VERTS; i++) {
|
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 bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0;
|
||||||
const float BRIGHTNESS = IS_MAIN
|
const float BRIGHTNESS = IS_MAIN
|
||||||
? Defaults::Playfield::GRID_BRIGHTNESS
|
? Defaults::Playfield::GRID_BRIGHTNESS
|
||||||
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
|
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
|
||||||
verticals.push_back(Line{
|
verticals.push_back(Line{
|
||||||
.start = {.x = X, .y = zona.y},
|
.start = {.x = X, .y = zone.y},
|
||||||
.end = {.x = X, .y = zona.y + zona.h},
|
.end = {.x = X, .y = zone.y + zone.h},
|
||||||
.brightness = BRIGHTNESS,
|
.brightness = BRIGHTNESS,
|
||||||
.spawn_time_s = 0.0F,
|
.spawn_time_s = 0.0F,
|
||||||
.is_vertical = true});
|
.is_vertical = true});
|
||||||
@@ -169,14 +169,14 @@ namespace Graphics {
|
|||||||
|
|
||||||
// Horitzontals: posicions j ∈ [1, SUB_HORIZ-1].
|
// Horitzontals: posicions j ∈ [1, SUB_HORIZ-1].
|
||||||
for (int j = 1; j < SUB_HORIZ; j++) {
|
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 bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0;
|
||||||
const float BRIGHTNESS = IS_MAIN
|
const float BRIGHTNESS = IS_MAIN
|
||||||
? Defaults::Playfield::GRID_BRIGHTNESS
|
? Defaults::Playfield::GRID_BRIGHTNESS
|
||||||
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
|
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
|
||||||
horizontals.push_back(Line{
|
horizontals.push_back(Line{
|
||||||
.start = {.x = zona.x, .y = Y},
|
.start = {.x = zone.x, .y = Y},
|
||||||
.end = {.x = zona.x + zona.w, .y = Y},
|
.end = {.x = zone.x + zone.w, .y = Y},
|
||||||
.brightness = BRIGHTNESS,
|
.brightness = BRIGHTNESS,
|
||||||
.spawn_time_s = 0.0F,
|
.spawn_time_s = 0.0F,
|
||||||
.is_vertical = false});
|
.is_vertical = false});
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ namespace Graphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StarfieldParallax::buildStars() {
|
void StarfieldParallax::buildStars() {
|
||||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
|
||||||
const float MIN_X = zona.x;
|
const float MIN_X = zone.x;
|
||||||
const float MAX_X = zona.x + zona.w;
|
const float MAX_X = zone.x + zone.w;
|
||||||
const float MIN_Y = zona.y;
|
const float MIN_Y = zone.y;
|
||||||
const float MAX_Y = zona.y + zona.h;
|
const float MAX_Y = zone.y + zone.h;
|
||||||
|
|
||||||
// Color únic per a totes les estrelles: el mateix blanc-blau gel
|
// Color únic per a totes les estrelles: el mateix blanc-blau gel
|
||||||
// del starfield del títol (Defaults::Title::Colors::STARFIELD).
|
// del starfield del títol (Defaults::Title::Colors::STARFIELD).
|
||||||
@@ -89,13 +89,13 @@ namespace Graphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StarfieldParallax::update(float delta_time, Vec2 world_velocity) {
|
void StarfieldParallax::update(float delta_time, Vec2 world_velocity) {
|
||||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
|
||||||
const float MIN_X = zona.x;
|
const float MIN_X = zone.x;
|
||||||
const float MAX_X = zona.x + zona.w;
|
const float MAX_X = zone.x + zone.w;
|
||||||
const float MIN_Y = zona.y;
|
const float MIN_Y = zone.y;
|
||||||
const float MAX_Y = zona.y + zona.h;
|
const float MAX_Y = zone.y + zone.h;
|
||||||
const float W = zona.w;
|
const float W = zone.w;
|
||||||
const float H = zona.h;
|
const float H = zone.h;
|
||||||
|
|
||||||
for (auto& star : stars_) {
|
for (auto& star : stars_) {
|
||||||
const float FACTOR = layerParallax(star.layer);
|
const float FACTOR = layerParallax(star.layer);
|
||||||
|
|||||||
@@ -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
|
// Calcular dimensions del text
|
||||||
float text_width = getTextWidth(text, scale, spacing);
|
float text_width = getTextWidth(text, scale, spacing);
|
||||||
float text_height = getTextHeight(scale);
|
float text_height = getTextHeight(scale);
|
||||||
|
|
||||||
// Calcular posición de l'esquina superior izquierda
|
// Calcular posición de l'esquina superior izquierda
|
||||||
// restant la meitat de las dimensions del point central
|
// restant la meitat de las dimensions del point central
|
||||||
Vec2 posicio_esquerra = {
|
Vec2 top_left_position = {
|
||||||
.x = centre_punt.x - (text_width / 2.0F),
|
.x = centre_point.x - (text_width / 2.0F),
|
||||||
.y = centre_punt.y - (text_height / 2.0F)};
|
.y = centre_point.y - (text_height / 2.0F)};
|
||||||
|
|
||||||
// Delegar al método render() existent
|
// 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 {
|
auto VectorText::getTextWidth(const std::string& text, float scale, float spacing) -> float {
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ namespace Graphics {
|
|||||||
|
|
||||||
// Renderizar string centrado en un punto
|
// Renderizar string centrado en un punto
|
||||||
// - text: cadena a renderizar
|
// - 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)
|
// - scale: factor de scale (1.0 = 20×40 px por carácter)
|
||||||
// - spacing: espacio entre caracteres en píxeles (a scale 1.0)
|
// - 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)
|
// - 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
|
// - 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).
|
// Calcular ancho total de un string (útil para centrado).
|
||||||
// Es estático: no depende del estado del VectorText (el ancho viene de
|
// Es estático: no depende del estado del VectorText (el ancho viene de
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace Physics {
|
|||||||
return false;
|
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 = (a.getCollisionRadius() + b.getCollisionRadius()) * amplifier;
|
||||||
float suma_radis_sq = suma_radis * suma_radis;
|
float suma_radis_sq = suma_radis * suma_radis;
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ namespace Physics {
|
|||||||
return dist_sq <= suma_radis_sq;
|
return dist_sq <= suma_radis_sq;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swept collision: una entitat mòbil (radi r_a) s'ha desplaçat de p0 a p1 aquest
|
// 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 radi conjunt (r_a + radi de b, amb
|
// 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
|
// amplificador) toca el cercle de l'entity b. Equival al check discrete quan
|
||||||
// p0 == p1 (sense moviment). Evita tunneling a velocitats altes.
|
// 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 {
|
inline auto checkCollisionSwept(const Vec2& p0, const Vec2& p1, float r_a, const Entities::Entity& b, float amplifier = 1.0F) -> bool {
|
||||||
|
|||||||
@@ -4,52 +4,52 @@
|
|||||||
|
|
||||||
namespace GameConfig {
|
namespace GameConfig {
|
||||||
|
|
||||||
// Mode de juego
|
// Mode de juego
|
||||||
enum class Mode : std::uint8_t {
|
enum class Mode : std::uint8_t {
|
||||||
NORMAL, // Partida normal
|
NORMAL, // Partida normal
|
||||||
DEMO // Mode demostració (futur)
|
DEMO // Mode demostració (futur)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Configuración de una match
|
// Configuración de una match
|
||||||
struct MatchConfig {
|
struct MatchConfig {
|
||||||
bool jugador1_actiu{false}; // Es active el player 1?
|
bool player1_active{false}; // Es active el player 1?
|
||||||
bool jugador2_actiu{false}; // Es active el player 2?
|
bool player2_active{false}; // Es active el player 2?
|
||||||
Mode mode{Mode::NORMAL}; // Mode de juego
|
Mode mode{Mode::NORMAL}; // Mode de juego
|
||||||
|
|
||||||
// Métodos auxiliars
|
// Métodos auxiliars
|
||||||
|
|
||||||
// Retorna true si solo hay un player active
|
// Retorna true si solo hay un player active
|
||||||
[[nodiscard]] auto isSinglePlayer() const -> bool {
|
[[nodiscard]] auto isSinglePlayer() const -> bool {
|
||||||
return (jugador1_actiu && !jugador2_actiu) ||
|
return (player1_active && !player2_active) ||
|
||||||
(!jugador1_actiu && jugador2_actiu);
|
(!player1_active && player2_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retorna true si hay dos jugadors active
|
// Retorna true si hay dos jugadors active
|
||||||
[[nodiscard]] auto isCoop() const -> bool {
|
[[nodiscard]] auto isCoop() const -> bool {
|
||||||
return jugador1_actiu && jugador2_actiu;
|
return player1_active && player2_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retorna true si no hay sin player active
|
// Retorna true si no hay sin player active
|
||||||
[[nodiscard]] auto hasNoPlayers() const -> bool {
|
[[nodiscard]] auto hasNoPlayers() const -> bool {
|
||||||
return !jugador1_actiu && !jugador2_actiu;
|
return !player1_active && !player2_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compte de jugadors active (0, 1 o 2)
|
// Compte de jugadors active (0, 1 o 2)
|
||||||
[[nodiscard]] auto getPlayerCount() const -> uint8_t {
|
[[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)
|
// Retorna l'ID de l'únic player active (0 o 1)
|
||||||
// Solo vàlid si es_un_jugador() retorna true
|
// Solo vàlid si es_un_jugador() retorna true
|
||||||
[[nodiscard]] auto getSinglePlayerId() const -> uint8_t {
|
[[nodiscard]] auto getSinglePlayerId() const -> uint8_t {
|
||||||
if (jugador1_actiu && !jugador2_actiu) {
|
if (player1_active && !player2_active) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!jugador1_actiu && jugador2_actiu) {
|
if (!player1_active && player2_active) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0; // Fallback (necesario comprovar es_un_jugador() primer)
|
return 0; // Fallback (necesario comprovar es_un_jugador() primer)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace GameConfig
|
} // namespace GameConfig
|
||||||
|
|||||||
+19
-19
@@ -7,40 +7,40 @@
|
|||||||
namespace Constants {
|
namespace Constants {
|
||||||
// Límits de objectes
|
// Límits de objectes
|
||||||
constexpr int MAX_ORNIS = Defaults::Entities::MAX_ORNIS;
|
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
|
// Matemàtiques
|
||||||
constexpr float PI = Defaults::Math::PI;
|
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 {
|
inline auto isInPlayArea(float x, float y) -> bool {
|
||||||
const SDL_FPoint POINT = {x, y};
|
const SDL_FPoint POINT = {x, y};
|
||||||
return SDL_PointInRectFloat(&POINT, &Defaults::Zones::PLAYAREA);
|
return SDL_PointInRectFloat(&POINT, &Defaults::Zones::PLAYAREA);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void getPlayAreaBounds(float& min_x, float& max_x, float& min_y, float& max_y) {
|
inline void getPlayAreaBounds(float& min_x, float& max_x, float& min_y, float& max_y) {
|
||||||
const auto& zona = Defaults::Zones::PLAYAREA;
|
const auto& zone = Defaults::Zones::PLAYAREA;
|
||||||
min_x = zona.x;
|
min_x = zone.x;
|
||||||
max_x = zona.x + zona.w;
|
max_x = zone.x + zone.w;
|
||||||
min_y = zona.y;
|
min_y = zone.y;
|
||||||
max_y = zona.y + zona.h;
|
max_y = zone.y + zone.h;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir límits segurs (compensant radi de l'entidad)
|
// Obtenir límits segurs (compensant radius de l'entidad)
|
||||||
inline void getSafePlayAreaBounds(float radi, float& min_x, float& max_x, float& min_y, float& max_y) {
|
inline void getSafePlayAreaBounds(float radius, float& min_x, float& max_x, float& min_y, float& max_y) {
|
||||||
const auto& zona = Defaults::Zones::PLAYAREA;
|
const auto& zone = Defaults::Zones::PLAYAREA;
|
||||||
constexpr float MARGE_SEGURETAT = 10.0F; // Safety margin
|
constexpr float SAFETY_MARGIN = 10.0F; // Safety margin
|
||||||
|
|
||||||
min_x = zona.x + radi + MARGE_SEGURETAT;
|
min_x = zone.x + radius + SAFETY_MARGIN;
|
||||||
max_x = zona.x + zona.w - radi - MARGE_SEGURETAT;
|
max_x = zone.x + zone.w - radius - SAFETY_MARGIN;
|
||||||
min_y = zona.y + radi + MARGE_SEGURETAT;
|
min_y = zone.y + radius + SAFETY_MARGIN;
|
||||||
max_y = zona.y + zona.h - radi - MARGE_SEGURETAT;
|
max_y = zone.y + zone.h - radius - SAFETY_MARGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir centro de l'àrea de juego
|
// Obtenir centro de l'àrea de juego
|
||||||
inline void getPlayAreaCenter(float& centre_x, float& centre_y) {
|
inline void getPlayAreaCenter(float& center_x, float& center_y) {
|
||||||
const auto& zona = Defaults::Zones::PLAYAREA;
|
const auto& zone = Defaults::Zones::PLAYAREA;
|
||||||
centre_x = zona.x + (zona.w / 2.0F);
|
center_x = zone.x + (zone.w / 2.0F);
|
||||||
centre_y = zona.y + (zona.h / 2.0F);
|
center_y = zone.y + (zone.h / 2.0F);
|
||||||
}
|
}
|
||||||
} // namespace Constants
|
} // namespace Constants
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Effects {
|
|||||||
// Política: viu sempre durant min_lifetime, després mor quan
|
// Política: viu sempre durant min_lifetime, després mor quan
|
||||||
// |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els
|
// |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els
|
||||||
// fragments ràpids no "popen" en moviment.
|
// 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)
|
float min_lifetime; // Temps mínim garantit (segons)
|
||||||
bool active; // Està actiu?
|
bool active; // Està actiu?
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ namespace Effects {
|
|||||||
float speed =
|
float speed =
|
||||||
velocitat_base +
|
velocitat_base +
|
||||||
(((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
|
(((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.x = (direccio.x * speed) + velocitat_objecte.x;
|
||||||
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
|
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
|
||||||
debris->acceleration = friction;
|
debris->acceleration = friction;
|
||||||
@@ -150,7 +150,7 @@ namespace Effects {
|
|||||||
|
|
||||||
// Vida i shrinking — min_lifetime és el temps mínim garantit; després
|
// Vida i shrinking — min_lifetime és el temps mínim garantit; després
|
||||||
// el fragment mor quan |velocity| < MIN_SPEED_TO_DIE.
|
// el fragment mor quan |velocity| < MIN_SPEED_TO_DIE.
|
||||||
debris->temps_vida = 0.0F;
|
debris->elapsed_time = 0.0F;
|
||||||
debris->min_lifetime = lifetime;
|
debris->min_lifetime = lifetime;
|
||||||
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
|
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
|
||||||
|
|
||||||
@@ -170,16 +170,16 @@ namespace Effects {
|
|||||||
|
|
||||||
// FASE 1: Aplicar herència i variació
|
// FASE 1: Aplicar herència i variació
|
||||||
float factor_herencia =
|
float factor_herencia =
|
||||||
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN +
|
Defaults::Physics::Debris::INHERITANCE_FACTOR_MIN +
|
||||||
((std::rand() / static_cast<float>(RAND_MAX)) *
|
((std::rand() / static_cast<float>(RAND_MAX)) *
|
||||||
(Defaults::Physics::Debris::FACTOR_HERENCIA_MAX -
|
(Defaults::Physics::Debris::INHERITANCE_FACTOR_MAX -
|
||||||
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN));
|
Defaults::Physics::Debris::INHERITANCE_FACTOR_MIN));
|
||||||
float velocitat_ang_heretada = velocitat_angular * factor_herencia;
|
float velocitat_ang_heretada = velocitat_angular * factor_herencia;
|
||||||
float variacio = ((std::rand() / static_cast<float>(RAND_MAX)) * 0.2F) - 0.1F;
|
float variacio = ((std::rand() / static_cast<float>(RAND_MAX)) * 0.2F) - 0.1F;
|
||||||
velocitat_ang_heretada *= (1.0F + variacio);
|
velocitat_ang_heretada *= (1.0F + variacio);
|
||||||
|
|
||||||
// FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial
|
// 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 abs_ang = std::abs(velocitat_ang_heretada);
|
||||||
float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F;
|
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)
|
// Rotación visual aleatòria (factor = 0.0 o sin velocidad angular)
|
||||||
debris.velocitat_rot_visual =
|
debris.velocitat_rot_visual =
|
||||||
Defaults::Physics::Debris::ROTACIO_MIN +
|
Defaults::Physics::Debris::ROTATION_MIN +
|
||||||
((std::rand() / static_cast<float>(RAND_MAX)) *
|
((std::rand() / static_cast<float>(RAND_MAX)) *
|
||||||
(Defaults::Physics::Debris::ROTACIO_MAX -
|
(Defaults::Physics::Debris::ROTATION_MAX -
|
||||||
Defaults::Physics::Debris::ROTACIO_MIN));
|
Defaults::Physics::Debris::ROTATION_MIN));
|
||||||
|
|
||||||
// 50% probabilitat de rotación en sentit contrari
|
// 50% probabilitat de rotación en sentit contrari
|
||||||
if (std::rand() % 2 == 0) {
|
if (std::rand() % 2 == 0) {
|
||||||
@@ -266,12 +266,12 @@ namespace Effects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Actualitzar time de vida
|
// 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
|
// 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
|
// quan la velocity cau per sota d'un llindar. Així els fragments
|
||||||
// ràpids no desapareixen en moviment.
|
// 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) +
|
const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) +
|
||||||
(debris.velocity.y * debris.velocity.y);
|
(debris.velocity.y * debris.velocity.y);
|
||||||
if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) {
|
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).
|
// 6. Shrink lineal sobre la longitud ORIGINAL (no iteratiu).
|
||||||
// SHRINK_T va de 0 a 1 al llarg de min_lifetime; després queda
|
// 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).
|
// 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));
|
const float SHRINK_FACTOR = std::max(0.0F, 1.0F - (debris.factor_shrink * SHRINK_T));
|
||||||
|
|
||||||
// 7. Reconstruir p1/p2 des de la geometria autoritaritzada:
|
// 7. Reconstruir p1/p2 des de la geometria autoritaritzada:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Effects {
|
|||||||
// tail = head − velocity_normalitzada × current_length.
|
// tail = head − velocity_normalitzada × current_length.
|
||||||
//
|
//
|
||||||
// Cicle de vida:
|
// 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.
|
// de 0 a max_length. Brillor al màxim.
|
||||||
// Fase 2: current_length = max_length × (speed/initial_speed) i brillor
|
// Fase 2: current_length = max_length × (speed/initial_speed) i brillor
|
||||||
// amb la mateixa proporció. Mor quan length o brightness cauen sota
|
// 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 max_length; // Longitud màxima (final de la fase de creixement)
|
||||||
float grow_duration; // Temps de creixement de 0 a max_length (s)
|
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 initial_speed; // Speed inicial per a la proporció de fase 2
|
||||||
|
|
||||||
float brightness; // 0..1
|
float brightness; // 0..1
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Effects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FireworkManager::spawn(const Vec2& origen,
|
void FireworkManager::spawn(const Vec2& origin,
|
||||||
SDL_Color color,
|
SDL_Color color,
|
||||||
float initial_speed,
|
float initial_speed,
|
||||||
int n_points,
|
int n_points,
|
||||||
@@ -74,7 +74,7 @@ namespace Effects {
|
|||||||
|
|
||||||
// Notificar als subscriptors (playfield pulses, etc.).
|
// Notificar als subscriptors (playfield pulses, etc.).
|
||||||
if (spawn_callback_) {
|
if (spawn_callback_) {
|
||||||
spawn_callback_(origen);
|
spawn_callback_(origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
const float ANGLE_STEP = 2.0F * Defaults::Math::PI / static_cast<float>(n_points);
|
const float ANGLE_STEP = 2.0F * Defaults::Math::PI / static_cast<float>(n_points);
|
||||||
@@ -94,7 +94,7 @@ namespace Effects {
|
|||||||
const float SPEED =
|
const float SPEED =
|
||||||
initial_speed + (randSigned() * Defaults::FX::Firework::SPEED_VARIATION);
|
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->velocity = {.x = std::cos(ANGLE) * SPEED, .y = std::sin(ANGLE) * SPEED};
|
||||||
fw->acceleration = Defaults::FX::Firework::FRICTION;
|
fw->acceleration = Defaults::FX::Firework::FRICTION;
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ namespace Effects {
|
|||||||
fw->max_length = Defaults::FX::Firework::MAX_LENGTH;
|
fw->max_length = Defaults::FX::Firework::MAX_LENGTH;
|
||||||
fw->grow_duration = Defaults::FX::Firework::GROW_DURATION;
|
fw->grow_duration = Defaults::FX::Firework::GROW_DURATION;
|
||||||
|
|
||||||
fw->temps_vida = 0.0F;
|
fw->elapsed_time = 0.0F;
|
||||||
fw->initial_speed = SPEED;
|
fw->initial_speed = SPEED;
|
||||||
|
|
||||||
fw->brightness = initial_brightness;
|
fw->brightness = initial_brightness;
|
||||||
@@ -119,7 +119,7 @@ namespace Effects {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fw.temps_vida += delta_time;
|
fw.elapsed_time += delta_time;
|
||||||
|
|
||||||
// 1. Fricció lineal (aplicar en la direcció del movement).
|
// 1. Fricció lineal (aplicar en la direcció del movement).
|
||||||
const float SPEED = std::sqrt(
|
const float SPEED = std::sqrt(
|
||||||
@@ -144,9 +144,9 @@ namespace Effects {
|
|||||||
bounceOffPlayArea(fw.head, fw.velocity);
|
bounceOffPlayArea(fw.head, fw.velocity);
|
||||||
|
|
||||||
// 4. Calcular longitud i brillor segons fase.
|
// 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.
|
// 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.current_length = fw.max_length * T;
|
||||||
fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS;
|
fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Effects {
|
|||||||
class FireworkManager {
|
class FireworkManager {
|
||||||
public:
|
public:
|
||||||
// Notificació opcional cada vegada que es genera un burst.
|
// 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);
|
explicit FireworkManager(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ namespace Effects {
|
|||||||
// initial_brightness: 0..1.
|
// initial_brightness: 0..1.
|
||||||
// glow: si true, cada partícula es renderitza amb halo neon.
|
// 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.
|
// 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,
|
SDL_Color color = Defaults::FX::Firework::DEFAULT_COLOR,
|
||||||
float initial_speed = Defaults::FX::Firework::SPEED,
|
float initial_speed = Defaults::FX::Firework::SPEED,
|
||||||
int n_points = Defaults::FX::Firework::N_POINTS,
|
int n_points = Defaults::FX::Firework::N_POINTS,
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
namespace Effects {
|
namespace Effects {
|
||||||
|
|
||||||
// FloatingScore: text animat que muestra points guanyats
|
// FloatingScore: text animat que muestra points guanyats
|
||||||
// S'activa cuando es destrueix un enemy i s'esvaeix después de un time
|
// S'activa cuando es destrueix un enemy i s'esvaeix después de un time
|
||||||
struct FloatingScore {
|
struct FloatingScore {
|
||||||
// Text a mostrar (e.g., "100", "150", "200")
|
// Text a mostrar (e.g., "100", "150", "200")
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
||||||
@@ -22,12 +22,12 @@ struct FloatingScore {
|
|||||||
Vec2 velocity; // px/s (normalment sin amunt: {0.0f, -30.0f})
|
Vec2 velocity; // px/s (normalment sin amunt: {0.0f, -30.0f})
|
||||||
|
|
||||||
// Animación de fade
|
// Animación de fade
|
||||||
float temps_vida; // Temps transcorregut (segons)
|
float elapsed_time; // Temps transcorregut (segons)
|
||||||
float temps_max; // Temps de vida màxim (segons)
|
float max_lifetime; // Temps de vida màxim (segons)
|
||||||
float brightness; // Brillantor calculada (0.0-1.0)
|
float brightness; // Brillantor calculada (0.0-1.0)
|
||||||
|
|
||||||
// Estat
|
// Estat
|
||||||
bool active;
|
bool active;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Effects
|
} // namespace Effects
|
||||||
|
|||||||
@@ -7,93 +7,93 @@
|
|||||||
|
|
||||||
namespace Effects {
|
namespace Effects {
|
||||||
|
|
||||||
FloatingScoreManager::FloatingScoreManager(Rendering::Renderer* renderer)
|
FloatingScoreManager::FloatingScoreManager(Rendering::Renderer* renderer)
|
||||||
: text_(renderer) {
|
: text_(renderer) {
|
||||||
// Inicialitzar todos los slots como inactius
|
// Inicialitzar todos los slots como inactius
|
||||||
for (auto& pf : pool_) {
|
for (auto& pf : pool_) {
|
||||||
pf.active = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FloatingScoreManager::crear(int points, const Vec2& position) {
|
|
||||||
// 1. Trobar slot lliure
|
|
||||||
FloatingScore* pf = findFreeSlot();
|
|
||||||
if (pf == nullptr) {
|
|
||||||
return; // Pool ple (improbable)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Inicialitzar puntuación flotante
|
|
||||||
pf->text = std::to_string(points);
|
|
||||||
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->brightness = 1.0F;
|
|
||||||
pf->active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FloatingScoreManager::update(float delta_time) {
|
|
||||||
for (auto& pf : pool_) {
|
|
||||||
if (!pf.active) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Actualitzar posición (deriva sin amunt)
|
|
||||||
pf.position.x += pf.velocity.x * delta_time;
|
|
||||||
pf.position.y += pf.velocity.y * delta_time;
|
|
||||||
|
|
||||||
// 2. Actualitzar time de vida
|
|
||||||
pf.temps_vida += delta_time;
|
|
||||||
|
|
||||||
// 3. Calcular brightness (fade lineal)
|
|
||||||
float progress = pf.temps_vida / pf.temps_max; // 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) {
|
|
||||||
pf.active = false;
|
pf.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void FloatingScoreManager::draw() {
|
void FloatingScoreManager::crear(int points, const Vec2& position) {
|
||||||
for (const auto& pf : pool_) {
|
// 1. Trobar slot lliure
|
||||||
if (!pf.active) {
|
FloatingScore* pf = findFreeSlot();
|
||||||
continue;
|
if (pf == nullptr) {
|
||||||
|
return; // Pool ple (improbable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderizar centrat con brightness (fade)
|
// 2. Inicialitzar puntuación flotante
|
||||||
constexpr float SCALE = Defaults::FloatingScore::SCALE;
|
pf->text = std::to_string(points);
|
||||||
constexpr float SPACING = Defaults::FloatingScore::SPACING;
|
pf->position = position;
|
||||||
|
pf->velocity = {.x = Defaults::FloatingScore::VELOCITY_X,
|
||||||
text_.renderCentered(pf.text, pf.position, SCALE, SPACING, pf.brightness);
|
.y = Defaults::FloatingScore::VELOCITY_Y};
|
||||||
|
pf->elapsed_time = 0.0F;
|
||||||
|
pf->max_lifetime = Defaults::FloatingScore::LIFETIME;
|
||||||
|
pf->brightness = 1.0F;
|
||||||
|
pf->active = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void FloatingScoreManager::reset() {
|
void FloatingScoreManager::update(float delta_time) {
|
||||||
for (auto& pf : pool_) {
|
for (auto& pf : pool_) {
|
||||||
pf.active = false;
|
if (!pf.active) {
|
||||||
}
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto FloatingScoreManager::getActiveCount() const -> int {
|
// 1. Actualitzar posición (deriva sin amunt)
|
||||||
int count = 0;
|
pf.position.x += pf.velocity.x * delta_time;
|
||||||
for (const auto& pf : pool_) {
|
pf.position.y += pf.velocity.y * delta_time;
|
||||||
if (pf.active) {
|
|
||||||
count++;
|
// 2. Actualitzar time de vida
|
||||||
|
pf.elapsed_time += delta_time;
|
||||||
|
|
||||||
|
// 3. Calcular brightness (fade lineal)
|
||||||
|
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.elapsed_time >= pf.max_lifetime) {
|
||||||
|
pf.active = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto FloatingScoreManager::findFreeSlot() -> FloatingScore* {
|
void FloatingScoreManager::draw() {
|
||||||
for (auto& pf : pool_) {
|
for (const auto& pf : pool_) {
|
||||||
if (!pf.active) {
|
if (!pf.active) {
|
||||||
return &pf;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderizar centrat con brightness (fade)
|
||||||
|
constexpr float SCALE = Defaults::FloatingScore::SCALE;
|
||||||
|
constexpr float SPACING = Defaults::FloatingScore::SPACING;
|
||||||
|
|
||||||
|
text_.renderCentered(pf.text, pf.position, SCALE, SPACING, pf.brightness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr; // Pool ple
|
|
||||||
}
|
void FloatingScoreManager::reset() {
|
||||||
|
for (auto& pf : pool_) {
|
||||||
|
pf.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto FloatingScoreManager::getActiveCount() const -> int {
|
||||||
|
int count = 0;
|
||||||
|
for (const auto& pf : pool_) {
|
||||||
|
if (pf.active) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto FloatingScoreManager::findFreeSlot() -> FloatingScore* {
|
||||||
|
for (auto& pf : pool_) {
|
||||||
|
if (!pf.active) {
|
||||||
|
return &pf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr; // Pool ple
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Effects
|
} // namespace Effects
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ void Bullet::init() {
|
|||||||
body_.clearAccumulators();
|
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
|
// Activar bullet
|
||||||
is_active_ = true;
|
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*/) {
|
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
|
// Systems::Collision::desactivateOutOfBoundsBullets() perquè així té accés
|
||||||
// al DebrisManager i pot generar el "trencament" visual de la bala alhora.
|
// al DebrisManager i pot generar el "trencament" visual de la bala alhora.
|
||||||
// El moviment l'integra PhysicsWorld; postUpdate sincronitza center_ i prev_position_.
|
// El moviment l'integra PhysicsWorld; postUpdate sincronitza center_ i prev_position_.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Bullet : public Entities::Entity {
|
|||||||
explicit Bullet(Rendering::Renderer* renderer);
|
explicit Bullet(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
void init() override;
|
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 update(float delta_time) override;
|
||||||
void postUpdate(float delta_time) override;
|
void postUpdate(float delta_time) override;
|
||||||
void draw() const override;
|
void draw() const override;
|
||||||
|
|||||||
+110
-110
@@ -41,7 +41,7 @@ namespace {
|
|||||||
Enemy::Enemy(Rendering::Renderer* renderer)
|
Enemy::Enemy(Rendering::Renderer* renderer)
|
||||||
: Entity(renderer),
|
: Entity(renderer),
|
||||||
|
|
||||||
tracking_strength_(Defaults::Enemies::Cuadrado::TRACKING_STRENGTH) {
|
tracking_strength_(Defaults::Enemies::Square::TRACKING_STRENGTH) {
|
||||||
brightness_ = Defaults::Brightness::ENEMIC;
|
brightness_ = Defaults::Brightness::ENEMIC;
|
||||||
|
|
||||||
// Configuración del cuerpo físico — defaults para enemy genérico.
|
// 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;
|
const char* shape_file = nullptr;
|
||||||
float base_speed = 0.0F;
|
float base_speed = 0.0F;
|
||||||
float drotacio_min = 0.0F;
|
float rotation_delta_min = 0.0F;
|
||||||
float drotacio_max = 0.0F;
|
float rotation_delta_max = 0.0F;
|
||||||
float type_mass = Defaults::Enemies::Body::DEFAULT_MASS;
|
float type_mass = Defaults::Enemies::Body::DEFAULT_MASS;
|
||||||
|
|
||||||
switch (type_) {
|
switch (type_) {
|
||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
|
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
|
||||||
base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
|
base_speed = Defaults::Enemies::Pentagon::SPEED;
|
||||||
drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
|
rotation_delta_min = Defaults::Enemies::Pentagon::ROTATION_DELTA_MIN;
|
||||||
drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
|
rotation_delta_max = Defaults::Enemies::Pentagon::ROTATION_DELTA_MAX;
|
||||||
type_mass = Defaults::Enemies::Pentagon::MASS;
|
type_mass = Defaults::Enemies::Pentagon::MASS;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
shape_file = Defaults::Enemies::Cuadrado::SHAPE_FILE;
|
shape_file = Defaults::Enemies::Square::SHAPE_FILE;
|
||||||
base_speed = Defaults::Enemies::Cuadrado::VELOCITAT;
|
base_speed = Defaults::Enemies::Square::SPEED;
|
||||||
drotacio_min = Defaults::Enemies::Cuadrado::DROTACIO_MIN;
|
rotation_delta_min = Defaults::Enemies::Square::ROTATION_DELTA_MIN;
|
||||||
drotacio_max = Defaults::Enemies::Cuadrado::DROTACIO_MAX;
|
rotation_delta_max = Defaults::Enemies::Square::ROTATION_DELTA_MAX;
|
||||||
type_mass = Defaults::Enemies::Cuadrado::MASS;
|
type_mass = Defaults::Enemies::Square::MASS;
|
||||||
tracking_timer_ = 0.0F;
|
tracking_timer_ = 0.0F;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
shape_file = Defaults::Enemies::Molinillo::SHAPE_FILE;
|
shape_file = Defaults::Enemies::Pinwheel::SHAPE_FILE;
|
||||||
base_speed = Defaults::Enemies::Molinillo::VELOCITAT;
|
base_speed = Defaults::Enemies::Pinwheel::SPEED;
|
||||||
drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN;
|
rotation_delta_min = Defaults::Enemies::Pinwheel::ROTATION_DELTA_MIN;
|
||||||
drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX;
|
rotation_delta_max = Defaults::Enemies::Pinwheel::ROTATION_DELTA_MAX;
|
||||||
type_mass = Defaults::Enemies::Molinillo::MASS;
|
type_mass = Defaults::Enemies::Pinwheel::MASS;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
std::cerr << "[Enemy] Error: tipo desconocido ("
|
std::cerr << "[Enemy] Error: tipo desconocido ("
|
||||||
<< static_cast<int>(type_) << "), usando PENTAGON\n";
|
<< static_cast<int>(type_) << "), usando PENTAGON\n";
|
||||||
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
|
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
|
||||||
base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
|
base_speed = Defaults::Enemies::Pentagon::SPEED;
|
||||||
drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
|
rotation_delta_min = Defaults::Enemies::Pentagon::ROTATION_DELTA_MIN;
|
||||||
drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
|
rotation_delta_max = Defaults::Enemies::Pentagon::ROTATION_DELTA_MAX;
|
||||||
break;
|
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);
|
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_.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));
|
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";
|
<< Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS << " intentos\n";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -152,28 +152,28 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
|||||||
body_.clearAccumulators();
|
body_.clearAccumulators();
|
||||||
|
|
||||||
// Rotación visual aleatoria (independiente del body)
|
// Rotación visual aleatoria (independiente del body)
|
||||||
const float DROTACIO_RANGE = drotacio_max - drotacio_min;
|
const float ROTATION_DELTA_RANGE = rotation_delta_max - rotation_delta_min;
|
||||||
drotacio_ = drotacio_min + ((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DROTACIO_RANGE);
|
rotation_delta_ = rotation_delta_min + ((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * ROTATION_DELTA_RANGE);
|
||||||
rotacio_ = 0.0F;
|
rotation_ = 0.0F;
|
||||||
|
|
||||||
// Estado de animación
|
// Estado de animación
|
||||||
animacio_ = EnemyAnimation();
|
animation_ = EnemyAnimation();
|
||||||
animacio_.drotacio_base = drotacio_;
|
animation_.rotation_delta_base = rotation_delta_;
|
||||||
animacio_.drotacio_objetivo = drotacio_;
|
animation_.rotation_delta_target = rotation_delta_;
|
||||||
animacio_.drotacio_t = 1.0F;
|
animation_.rotation_delta_t = 1.0F;
|
||||||
|
|
||||||
// Invulnerabilidad post-spawn
|
// Invulnerabilidad post-spawn
|
||||||
timer_invulnerabilitat_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
|
invulnerability_timer_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
|
||||||
brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
||||||
|
|
||||||
// Timer para próximo cambio de dirección (Pentagon)
|
// Timer para próximo cambio de dirección (Pentagon)
|
||||||
direction_change_timer_ = 0.0F;
|
direction_change_timer_ = 0.0F;
|
||||||
|
|
||||||
esta_ = true;
|
is_active_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::update(float delta_time) {
|
void Enemy::update(float delta_time) {
|
||||||
if (!esta_) {
|
if (!is_active_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,11 +189,11 @@ void Enemy::update(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decremento de invulnerabilidad + LERP de brightness
|
// Decremento de invulnerabilidad + LERP de brightness
|
||||||
if (timer_invulnerabilitat_ > 0.0F) {
|
if (invulnerability_timer_ > 0.0F) {
|
||||||
timer_invulnerabilitat_ -= delta_time;
|
invulnerability_timer_ -= delta_time;
|
||||||
timer_invulnerabilitat_ = std::max(timer_invulnerabilitat_, 0.0F);
|
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 T = 1.0F - T_INV;
|
||||||
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
||||||
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
||||||
@@ -209,11 +209,11 @@ void Enemy::update(float delta_time) {
|
|||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
behaviorPentagon(delta_time);
|
behaviorPentagon(delta_time);
|
||||||
break;
|
break;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
behaviorQuadrat(delta_time);
|
behaviorSquare(delta_time);
|
||||||
break;
|
break;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
behaviorMolinillo(delta_time);
|
behaviorPinwheel(delta_time);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,18 +222,18 @@ void Enemy::update(float delta_time) {
|
|||||||
updateAnimation(delta_time);
|
updateAnimation(delta_time);
|
||||||
|
|
||||||
// Rotación visual (decoración, no afecta movimiento)
|
// Rotación visual (decoración, no afecta movimiento)
|
||||||
rotacio_ += drotacio_ * delta_time;
|
rotation_ += rotation_delta_ * delta_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::postUpdate(float /*delta_time*/) {
|
void Enemy::postUpdate(float /*delta_time*/) {
|
||||||
// Sincronizar mirror tras la integración del world.
|
// Sincronizar mirror tras la integración del world.
|
||||||
if (esta_) {
|
if (is_active_) {
|
||||||
center_ = body_.position;
|
center_ = body_.position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::draw() const {
|
void Enemy::draw() const {
|
||||||
if (!esta_ || !shape_) {
|
if (!is_active_ || !shape_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const float SCALE = computeCurrentScale();
|
const float SCALE = computeCurrentScale();
|
||||||
@@ -242,11 +242,11 @@ void Enemy::draw() const {
|
|||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
color = Defaults::Palette::PENTAGON;
|
color = Defaults::Palette::PENTAGON;
|
||||||
break;
|
break;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
color = Defaults::Palette::QUADRAT;
|
color = Defaults::Palette::SQUARE;
|
||||||
break;
|
break;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
color = Defaults::Palette::MOLINILLO;
|
color = Defaults::Palette::PINWHEEL;
|
||||||
break;
|
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() {
|
void Enemy::destroy() {
|
||||||
esta_ = false;
|
is_active_ = false;
|
||||||
body_.velocity = Vec2{};
|
body_.velocity = Vec2{};
|
||||||
body_.angular_velocity = 0.0F;
|
body_.angular_velocity = 0.0F;
|
||||||
body_.radius = 0.0F; // No colisiona mientras está inactivo
|
body_.radius = 0.0F; // No colisiona mientras está inactivo
|
||||||
@@ -273,7 +273,7 @@ void Enemy::destruir() {
|
|||||||
last_hit_by_ = 0xFF;
|
last_hit_by_ = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::herir(uint8_t shooter_id) {
|
void Enemy::hurt(uint8_t shooter_id) {
|
||||||
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
|
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
|
||||||
last_hit_by_ = shooter_id;
|
last_hit_by_ = shooter_id;
|
||||||
// El so HIT ara el reprodueix la bala quan es trenca en debris
|
// 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) {
|
if (RAND_VAL < Defaults::Enemies::Pentagon::ZIGZAG_PROB_PER_SECOND * delta_time) {
|
||||||
const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
|
const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
|
||||||
const float DELTA = (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) *
|
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 NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
|
||||||
const float SPEED = body_.velocity.length();
|
const float SPEED = body_.velocity.length();
|
||||||
setVelocityFromAngle(NEW_ANGLE, SPEED);
|
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_.
|
// hacia el ship mezclando con tracking_strength_.
|
||||||
void Enemy::behaviorQuadrat(float delta_time) {
|
void Enemy::behaviorSquare(float delta_time) {
|
||||||
tracking_timer_ += 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;
|
tracking_timer_ = 0.0F;
|
||||||
|
|
||||||
const Vec2 TO_SHIP = *ship_position_ - center_;
|
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
|
// Sin tracking — solo cambios de dirección raros (igual que Pentagon pero
|
||||||
// con probabilidad mucho menor).
|
// 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
|
// Boost de rotación visual por proximidad al ship
|
||||||
if (ship_position_ != nullptr) {
|
if (ship_position_ != nullptr) {
|
||||||
const Vec2 TO_SHIP = *ship_position_ - center_;
|
const Vec2 TO_SHIP = *ship_position_ - center_;
|
||||||
const float DIST = TO_SHIP.length();
|
const float DIST = TO_SHIP.length();
|
||||||
if (DIST < Defaults::Enemies::Molinillo::PROXIMITY_DISTANCE) {
|
if (DIST < Defaults::Enemies::Pinwheel::PROXIMITY_DISTANCE) {
|
||||||
drotacio_ = animacio_.drotacio_base * Defaults::Enemies::Molinillo::DROTACIO_PROXIMITY_MULTIPLIER;
|
rotation_delta_ = animation_.rotation_delta_base * Defaults::Enemies::Pinwheel::ROTATION_DELTA_PROXIMITY_MULTIPLIER;
|
||||||
} else {
|
} else {
|
||||||
drotacio_ = animacio_.drotacio_base;
|
rotation_delta_ = animation_.rotation_delta_base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Movimiento lineal puro: el world se encarga de integrar y rebotar.
|
// Movimiento lineal puro: el world se encarga de integrar y rebotar.
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::updateAnimation(float delta_time) {
|
void Enemy::updateAnimation(float delta_time) {
|
||||||
updatePalpitation(delta_time);
|
updatePulse(delta_time);
|
||||||
updateRotationAcceleration(delta_time);
|
updateRotationAcceleration(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::updatePalpitation(float delta_time) {
|
void Enemy::updatePulse(float delta_time) {
|
||||||
if (animacio_.palpitacio_activa) {
|
if (animation_.pulse_active) {
|
||||||
animacio_.palpitacio_fase += 2.0F * Constants::PI * animacio_.palpitacio_frequencia * delta_time;
|
animation_.pulse_phase += 2.0F * Constants::PI * animation_.pulse_frequency * delta_time;
|
||||||
animacio_.palpitacio_temps_restant -= delta_time;
|
animation_.pulse_time_remaining -= delta_time;
|
||||||
if (animacio_.palpitacio_temps_restant <= 0.0F) {
|
if (animation_.pulse_time_remaining <= 0.0F) {
|
||||||
animacio_.palpitacio_activa = false;
|
animation_.pulse_active = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
|
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) {
|
if (RAND_VAL < TRIGGER_PROB) {
|
||||||
animacio_.palpitacio_activa = true;
|
animation_.pulse_active = true;
|
||||||
animacio_.palpitacio_fase = 0.0F;
|
animation_.pulse_phase = 0.0F;
|
||||||
|
|
||||||
const float FREQ_RANGE = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX -
|
const float FREQ_RANGE = Defaults::Enemies::Animation::PULSE_FREQ_MAX -
|
||||||
Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN;
|
Defaults::Enemies::Animation::PULSE_FREQ_MIN;
|
||||||
animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN +
|
animation_.pulse_frequency = Defaults::Enemies::Animation::PULSE_FREQ_MIN +
|
||||||
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * FREQ_RANGE);
|
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * FREQ_RANGE);
|
||||||
|
|
||||||
const float AMP_RANGE = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX -
|
const float AMP_RANGE = Defaults::Enemies::Animation::PULSE_AMPLITUD_MAX -
|
||||||
Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN;
|
Defaults::Enemies::Animation::PULSE_AMPLITUD_MIN;
|
||||||
animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN +
|
animation_.pulse_amplitude = Defaults::Enemies::Animation::PULSE_AMPLITUD_MIN +
|
||||||
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * AMP_RANGE);
|
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * AMP_RANGE);
|
||||||
|
|
||||||
const float DUR_RANGE = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX -
|
const float DUR_RANGE = Defaults::Enemies::Animation::PULSE_DURATION_MAX -
|
||||||
Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN;
|
Defaults::Enemies::Animation::PULSE_DURATION_MIN;
|
||||||
animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN +
|
animation_.pulse_time_remaining = Defaults::Enemies::Animation::PULSE_DURATION_MIN +
|
||||||
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DUR_RANGE);
|
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DUR_RANGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::updateRotationAcceleration(float delta_time) {
|
void Enemy::updateRotationAcceleration(float delta_time) {
|
||||||
if (animacio_.drotacio_t < 1.0F) {
|
if (animation_.rotation_delta_t < 1.0F) {
|
||||||
animacio_.drotacio_t += delta_time / animacio_.drotacio_duracio;
|
animation_.rotation_delta_t += delta_time / animation_.rotation_delta_duration;
|
||||||
if (animacio_.drotacio_t >= 1.0F) {
|
if (animation_.rotation_delta_t >= 1.0F) {
|
||||||
animacio_.drotacio_t = 1.0F;
|
animation_.rotation_delta_t = 1.0F;
|
||||||
animacio_.drotacio_base = animacio_.drotacio_objetivo;
|
animation_.rotation_delta_base = animation_.rotation_delta_target;
|
||||||
drotacio_ = animacio_.drotacio_base;
|
rotation_delta_ = animation_.rotation_delta_base;
|
||||||
} else {
|
} 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 SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
||||||
const float INITIAL = animacio_.drotacio_base;
|
const float INITIAL = animation_.rotation_delta_base;
|
||||||
const float TARGET = animacio_.drotacio_objetivo;
|
const float TARGET = animation_.rotation_delta_target;
|
||||||
drotacio_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
|
rotation_delta_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
|
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) {
|
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 -
|
const float MULT_RANGE = Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MAX -
|
||||||
Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN;
|
Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MIN;
|
||||||
const float MULTIPLIER = Defaults::Enemies::Animation::ROTACIO_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);
|
((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 -
|
const float DUR_RANGE = Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MAX -
|
||||||
Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN;
|
Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MIN;
|
||||||
animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN +
|
animation_.rotation_delta_duration = Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MIN +
|
||||||
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DUR_RANGE);
|
((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 {
|
auto Enemy::computeCurrentScale() const -> float {
|
||||||
float scale = 1.0F;
|
float scale = 1.0F;
|
||||||
if (timer_invulnerabilitat_ > 0.0F) {
|
if (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 T = 1.0F - T_INV;
|
||||||
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
||||||
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
|
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
|
||||||
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
|
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
|
||||||
scale = START + ((END - START) * SMOOTH_T);
|
scale = START + ((END - START) * SMOOTH_T);
|
||||||
} else if (animacio_.palpitacio_activa) {
|
} else if (animation_.pulse_active) {
|
||||||
scale += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase);
|
scale += animation_.pulse_amplitude * std::sin(animation_.pulse_phase);
|
||||||
}
|
}
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
@@ -454,22 +454,22 @@ auto Enemy::computeCurrentScale() const -> float {
|
|||||||
auto Enemy::getBaseVelocity() const -> float {
|
auto Enemy::getBaseVelocity() const -> float {
|
||||||
switch (type_) {
|
switch (type_) {
|
||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
return Defaults::Enemies::Pentagon::VELOCITAT;
|
return Defaults::Enemies::Pentagon::SPEED;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
return Defaults::Enemies::Cuadrado::VELOCITAT;
|
return Defaults::Enemies::Square::SPEED;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
return Defaults::Enemies::Molinillo::VELOCITAT;
|
return Defaults::Enemies::Pinwheel::SPEED;
|
||||||
default:
|
default:
|
||||||
return Defaults::Enemies::Pentagon::VELOCITAT;
|
return Defaults::Enemies::Pentagon::SPEED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Enemy::getBaseRotation() const -> float {
|
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) {
|
void Enemy::setTrackingStrength(float strength) {
|
||||||
if (type_ == EnemyType::QUADRAT) {
|
if (type_ == EnemyType::SQUARE) {
|
||||||
tracking_strength_ = strength;
|
tracking_strength_ = strength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,24 @@
|
|||||||
// Tipo de enemy
|
// Tipo de enemy
|
||||||
enum class EnemyType : uint8_t {
|
enum class EnemyType : uint8_t {
|
||||||
PENTAGON = 0, // Pentágono esquivador (zigzag)
|
PENTAGON = 0, // Pentágono esquivador (zigzag)
|
||||||
QUADRAT = 1, // Cuadrado perseguidor (tracks ship)
|
SQUARE = 1, // Square perseguidor (tracks ship)
|
||||||
MOLINILLO = 2 // Molinillo agresivo (rápido, girando)
|
PINWHEEL = 2 // Molinillo agresivo (rápido, girando)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Estado de animación (palpitación + rotación acelerada)
|
// Estado de animación (palpitación + rotación acelerada)
|
||||||
struct EnemyAnimation {
|
struct EnemyAnimation {
|
||||||
// Palpitación (efecto respiración)
|
// Palpitación (efecto respiración)
|
||||||
bool palpitacio_activa = false;
|
bool pulse_active = false;
|
||||||
float palpitacio_fase = 0.0F;
|
float pulse_phase = 0.0F;
|
||||||
float palpitacio_frequencia = 2.0F;
|
float pulse_frequency = 2.0F;
|
||||||
float palpitacio_amplitud = 0.15F;
|
float pulse_amplitude = 0.15F;
|
||||||
float palpitacio_temps_restant = 0.0F;
|
float pulse_time_remaining = 0.0F;
|
||||||
|
|
||||||
// Aceleración de rotación visual (modulación a largo plazo)
|
// Aceleración de rotación visual (modulación a largo plazo)
|
||||||
float drotacio_base = 0.0F;
|
float rotation_delta_base = 0.0F;
|
||||||
float drotacio_objetivo = 0.0F;
|
float rotation_delta_target = 0.0F;
|
||||||
float drotacio_t = 0.0F;
|
float rotation_delta_t = 0.0F;
|
||||||
float drotacio_duracio = 0.0F;
|
float rotation_delta_duration = 0.0F;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Enemy : public Entities::Entity {
|
class Enemy : public Entities::Entity {
|
||||||
@@ -46,7 +46,7 @@ class Enemy : public Entities::Entity {
|
|||||||
void draw() const override;
|
void draw() const override;
|
||||||
|
|
||||||
// Override: Interfaz de Entity
|
// 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
|
// Override: Interfaz de colisión
|
||||||
[[nodiscard]] auto getCollisionRadius() const -> float override {
|
[[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
|
// poden abatre i el cos físic rebota amb la nau. El damage a la nau
|
||||||
// segueix filtrat per `isInvulnerable()` al detectShipEnemy.
|
// segueix filtrat per `isInvulnerable()` al detectShipEnemy.
|
||||||
[[nodiscard]] auto isCollidable() const -> bool override {
|
[[nodiscard]] auto isCollidable() const -> bool override {
|
||||||
return esta_;
|
return is_active_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marcar destruido (desactiva el cuerpo físicamente: radius=0)
|
// Marcar destruido (desactiva el cuerpo físicamente: radius=0)
|
||||||
void destruir();
|
void destroy();
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
[[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; }
|
[[nodiscard]] auto getRotationDelta() const -> float { return rotation_delta_; }
|
||||||
[[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; }
|
[[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; }
|
||||||
|
|
||||||
// Set ship position reference for tracking behavior
|
// Set ship position reference for tracking behavior
|
||||||
@@ -79,18 +79,18 @@ class Enemy : public Entities::Entity {
|
|||||||
// actual del body_.velocity.
|
// actual del body_.velocity.
|
||||||
void setVelocity(float speed);
|
void setVelocity(float speed);
|
||||||
void setRotation(float rot) {
|
void setRotation(float rot) {
|
||||||
drotacio_ = rot;
|
rotation_delta_ = rot;
|
||||||
animacio_.drotacio_base = rot;
|
animation_.rotation_delta_base = rot;
|
||||||
}
|
}
|
||||||
void setTrackingStrength(float strength);
|
void setTrackingStrength(float strength);
|
||||||
|
|
||||||
// Invulnerabilidad
|
// Invulnerabilidad
|
||||||
[[nodiscard]] auto isInvulnerable() const -> bool { return timer_invulnerabilitat_ > 0.0F; }
|
[[nodiscard]] auto isInvulnerable() const -> bool { return invulnerability_timer_ > 0.0F; }
|
||||||
[[nodiscard]] auto getInvulnerabilityTime() const -> float { return timer_invulnerabilitat_; }
|
[[nodiscard]] auto getInvulnerabilityTime() const -> float { return invulnerability_timer_; }
|
||||||
|
|
||||||
// Estado "herido": entre primer impacto de bala y explosión diferida.
|
// Estado "herido": entre primer impacto de bala y explosión diferida.
|
||||||
// shooter_id: id del jugador que herí; 0xFF = sin atribución (cadena, etc.).
|
// 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 isWounded() const -> bool { return wounded_timer_ > 0.0F; }
|
||||||
[[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; }
|
[[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; }
|
||||||
[[nodiscard]] auto woundExpiredThisFrame() const -> bool { return wound_expired_this_frame_; }
|
[[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_).
|
// 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
|
// 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.
|
// 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 rotation_delta_{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)
|
float rotation_{0.0F}; // Rotación visual acumulada (no afecta movimiento)
|
||||||
bool esta_{false};
|
bool is_active_{false};
|
||||||
|
|
||||||
EnemyType type_{EnemyType::PENTAGON};
|
EnemyType type_{EnemyType::PENTAGON};
|
||||||
EnemyAnimation animacio_;
|
EnemyAnimation animation_;
|
||||||
|
|
||||||
// Comportamiento type-specific
|
// Comportamiento type-specific
|
||||||
float tracking_timer_{0.0F}; // Quadrat: tiempo desde último update de dirección
|
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
|
float direction_change_timer_{0.0F}; // Pentagon: tiempo para próximo cambio de dirección
|
||||||
|
|
||||||
// Invulnerabilidad post-spawn
|
// 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.
|
// Estado "herido": timer cuenta atrás; al cruzar 0 se marca expiración.
|
||||||
float wounded_timer_{0.0F};
|
float wounded_timer_{0.0F};
|
||||||
@@ -127,11 +127,11 @@ class Enemy : public Entities::Entity {
|
|||||||
|
|
||||||
// Métodos privados
|
// Métodos privados
|
||||||
void updateAnimation(float delta_time);
|
void updateAnimation(float delta_time);
|
||||||
void updatePalpitation(float delta_time);
|
void updatePulse(float delta_time);
|
||||||
void updateRotationAcceleration(float delta_time);
|
void updateRotationAcceleration(float delta_time);
|
||||||
void behaviorPentagon(float delta_time);
|
void behaviorPentagon(float delta_time);
|
||||||
void behaviorQuadrat(float delta_time);
|
void behaviorSquare(float delta_time);
|
||||||
void behaviorMolinillo(float delta_time);
|
void behaviorPinwheel(float delta_time);
|
||||||
[[nodiscard]] auto computeCurrentScale() const -> float;
|
[[nodiscard]] auto computeCurrentScale() const -> float;
|
||||||
// Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy.
|
// 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;
|
static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool;
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
|
|||||||
if (spawn_point != nullptr) {
|
if (spawn_point != nullptr) {
|
||||||
center_ = *spawn_point;
|
center_ = *spawn_point;
|
||||||
} else {
|
} else {
|
||||||
float centre_x;
|
float center_x;
|
||||||
float centre_y;
|
float center_y;
|
||||||
Constants::getPlayAreaCenter(centre_x, centre_y);
|
Constants::getPlayAreaCenter(center_x, center_y);
|
||||||
center_ = {.x = centre_x, .y = centre_y};
|
center_ = {.x = center_x, .y = center_y};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset orientación
|
// Reset orientación
|
||||||
@@ -180,7 +180,7 @@ void Ship::draw() const {
|
|||||||
Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, color);
|
Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ship::herir() {
|
void Ship::hurt() {
|
||||||
hurt_timer_ = Defaults::Ship::Hurt::DURATION;
|
hurt_timer_ = Defaults::Ship::Hurt::DURATION;
|
||||||
Audio::get()->playSound(Defaults::Sound::HURT, Audio::Group::GAME);
|
Audio::get()->playSound(Defaults::Sound::HURT, Audio::Group::GAME);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Ship : public Entities::Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Estat "ferit": primera col·lisió amb enemic dispara HURT; segona durant HURT mata.
|
// 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 isHurt() const -> bool { return hurt_timer_ > 0.0F; }
|
||||||
[[nodiscard]] auto getHurtTimer() const -> float { return hurt_timer_; }
|
[[nodiscard]] auto getHurtTimer() const -> float { return hurt_timer_; }
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
|
|
||||||
// Debug output de la configuración
|
// Debug output de la configuración
|
||||||
std::cout << "[GameScene] Configuración de match - P1: "
|
std::cout << "[GameScene] Configuración de match - P1: "
|
||||||
<< (match_config_.jugador1_actiu ? "ACTIU" : "INACTIU")
|
<< (match_config_.player1_active ? "ACTIU" : "INACTIU")
|
||||||
<< ", P2: "
|
<< ", P2: "
|
||||||
<< (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU")
|
<< (match_config_.player2_active ? "ACTIU" : "INACTIU")
|
||||||
<< '\n';
|
<< '\n';
|
||||||
|
|
||||||
// Consumir opciones (preparació per MODE_DEMO futur)
|
// 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
|
// Basat en el codi Pascal original: line 376
|
||||||
std::srand(static_cast<unsigned>(std::time(nullptr)));
|
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_.clear();
|
||||||
physics_world_.setBounds(Defaults::Zones::PLAYAREA);
|
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).
|
// Fireworks generen una ripple gran al playfield (ona d'aigua centrada al burst).
|
||||||
firework_manager_.setSpawnCallback([this](Vec2 origen) {
|
firework_manager_.setSpawnCallback([this](Vec2 origin) {
|
||||||
playfield_.notifyExplosion(origen);
|
playfield_.notifyExplosion(origin);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Explosions properes a una paret també generen bump (falloff lineal amb la distància).
|
// Explosions properes a una paret també generen bump (falloff lineal amb la distància).
|
||||||
debris_manager_.setExplosionCallback([this](Vec2 center) {
|
debris_manager_.setExplosionCallback([this](Vec2 center) {
|
||||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
|
||||||
const float DIST_LEFT = std::abs(center.x - zona.x);
|
const float DIST_LEFT = std::abs(center.x - zone.x);
|
||||||
const float DIST_RIGHT = std::abs((zona.x + zona.w) - center.x);
|
const float DIST_RIGHT = std::abs((zone.x + zone.w) - center.x);
|
||||||
const float DIST_TOP = std::abs(center.y - zona.y);
|
const float DIST_TOP = std::abs(center.y - zone.y);
|
||||||
const float DIST_BOTTOM = std::abs((zona.y + zona.h) - center.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});
|
const float MIN_DIST = std::min({DIST_LEFT, DIST_RIGHT, DIST_TOP, DIST_BOTTOM});
|
||||||
if (MIN_DIST > Defaults::Border::EXPLOSION_FALLOFF_PX) {
|
if (MIN_DIST > Defaults::Border::EXPLOSION_FALLOFF_PX) {
|
||||||
return;
|
return;
|
||||||
@@ -130,9 +130,9 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
|
|
||||||
// Inicialitzar naves segons configuración (solo jugadors active)
|
// Inicialitzar naves segons configuración (solo jugadors active)
|
||||||
for (uint8_t i = 0; i < 2; i++) {
|
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
|
// Jugador active: init normalment
|
||||||
Vec2 spawn_pos = getSpawnPoint(i);
|
Vec2 spawn_pos = getSpawnPoint(i);
|
||||||
ships_[i].init(&spawn_pos, false); // No invulnerability at start
|
ships_[i].init(&spawn_pos, false); // No invulnerability at start
|
||||||
@@ -248,11 +248,11 @@ void GameScene::stepPhysics(float delta_time) {
|
|||||||
|
|
||||||
void GameScene::stepShootingInput() {
|
void GameScene::stepShootingInput() {
|
||||||
auto* input = Input::get();
|
auto* input = Input::get();
|
||||||
if (match_config_.jugador1_actiu &&
|
if (match_config_.player1_active &&
|
||||||
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||||
fireBullet(0);
|
fireBullet(0);
|
||||||
}
|
}
|
||||||
if (match_config_.jugador2_actiu &&
|
if (match_config_.player2_active &&
|
||||||
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||||
fireBullet(1);
|
fireBullet(1);
|
||||||
}
|
}
|
||||||
@@ -267,16 +267,16 @@ void GameScene::stepMidGameJoin() {
|
|||||||
// Solo se permite join si hay al menos un jugador vivo (no se puede
|
// Solo se permite join si hay al menos un jugador vivo (no se puede
|
||||||
// hacer join en pantalla vacía).
|
// hacer join en pantalla vacía).
|
||||||
const bool ALGU_VIU =
|
const bool ALGU_VIU =
|
||||||
(match_config_.jugador1_actiu && hit_timer_per_player_[0] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER) ||
|
(match_config_.player1_active && 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_.player2_active && hit_timer_per_player_[1] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER);
|
||||||
if (!ALGU_VIU) {
|
if (!ALGU_VIU) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* input = Input::get();
|
auto* input = Input::get();
|
||||||
for (uint8_t pid = 0; pid < 2; pid++) {
|
for (uint8_t pid = 0; pid < 2; pid++) {
|
||||||
const bool ACTIU = (pid == 0) ? match_config_.jugador1_actiu
|
const bool ACTIU = (pid == 0) ? match_config_.player1_active
|
||||||
: match_config_.jugador2_actiu;
|
: match_config_.player2_active;
|
||||||
const bool MUERTO_SIN_VIDAS = hit_timer_per_player_[pid] == Defaults::Game::HIT_TIMER_INACTIVE_PLAYER;
|
const bool MUERTO_SIN_VIDAS = hit_timer_per_player_[pid] == Defaults::Game::HIT_TIMER_INACTIVE_PLAYER;
|
||||||
if (ACTIU && !MUERTO_SIN_VIDAS) {
|
if (ACTIU && !MUERTO_SIN_VIDAS) {
|
||||||
continue; // jugador ya está jugando
|
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.
|
// Sin vidas: marcar definitivamente muerto y comprobar transición a CONTINUE.
|
||||||
hit_timer_per_player_[i] = Defaults::Game::HIT_TIMER_INACTIVE_PLAYER;
|
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 P1_DEAD = !match_config_.player1_active || lives_per_player_[0] <= 0;
|
||||||
const bool P2_DEAD = !match_config_.jugador2_actiu || lives_per_player_[1] <= 0;
|
const bool P2_DEAD = !match_config_.player2_active || lives_per_player_[1] <= 0;
|
||||||
if (P1_DEAD && P2_DEAD) {
|
if (P1_DEAD && P2_DEAD) {
|
||||||
game_over_state_ = GameOverState::CONTINUE;
|
game_over_state_ = GameOverState::CONTINUE;
|
||||||
continue_counter_ = Defaults::Game::CONTINUE_COUNT_START;
|
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_INIT,
|
||||||
Defaults::Game::INIT_HUD_SHIP2_RATIO_END);
|
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)));
|
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)));
|
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.
|
// Ambas naves pueden moverse y disparar durante el intro.
|
||||||
for (uint8_t i = 0; i < 2; i++) {
|
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) {
|
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
|
||||||
ships_[i].processInput(delta_time, i);
|
ships_[i].processInput(delta_time, i);
|
||||||
ships_[i].update(delta_time);
|
ships_[i].update(delta_time);
|
||||||
@@ -481,7 +481,7 @@ void GameScene::runStagePlaying(float delta_time) {
|
|||||||
|
|
||||||
// Gameplay normal: ships activos + entidades + colisiones + efectos.
|
// Gameplay normal: ships activos + entidades + colisiones + efectos.
|
||||||
for (uint8_t i = 0; i < 2; i++) {
|
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) {
|
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
|
||||||
ships_[i].processInput(delta_time, i);
|
ships_[i].processInput(delta_time, i);
|
||||||
ships_[i].update(delta_time);
|
ships_[i].update(delta_time);
|
||||||
@@ -491,7 +491,7 @@ void GameScene::runStagePlaying(float delta_time) {
|
|||||||
enemy.update(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
|
// el mateix frame xoca amb un enemic i alhora surt del PLAYAREA es compta com a
|
||||||
// impacte abans no se la trenqui per sortir.
|
// impacte abans no se la trenqui per sortir.
|
||||||
runCollisionDetections();
|
runCollisionDetections();
|
||||||
@@ -507,7 +507,7 @@ void GameScene::runStagePlaying(float delta_time) {
|
|||||||
void GameScene::runStageLevelCompleted(float delta_time) {
|
void GameScene::runStageLevelCompleted(float delta_time) {
|
||||||
stage_manager_->update(delta_time);
|
stage_manager_->update(delta_time);
|
||||||
for (uint8_t i = 0; i < 2; i++) {
|
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) {
|
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
|
||||||
ships_[i].processInput(delta_time, i);
|
ships_[i].processInput(delta_time, i);
|
||||||
ships_[i].update(delta_time);
|
ships_[i].update(delta_time);
|
||||||
@@ -580,8 +580,8 @@ void GameScene::drawBullets() const {
|
|||||||
|
|
||||||
void GameScene::drawActiveShipsAlive() const {
|
void GameScene::drawActiveShipsAlive() const {
|
||||||
for (uint8_t i = 0; i < 2; i++) {
|
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 && hit_timer_per_player_[i] == 0.0F) {
|
if (player_active && hit_timer_per_player_[i] == 0.0F) {
|
||||||
ships_[i].draw();
|
ships_[i].draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -613,10 +613,10 @@ void GameScene::drawGameOverState() {
|
|||||||
constexpr float SPACING = Defaults::Game::GameOverScreen::TEXT_SPACING;
|
constexpr float SPACING = Defaults::Game::GameOverScreen::TEXT_SPACING;
|
||||||
|
|
||||||
const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
|
const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
|
||||||
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 = play_area.y + (play_area.h / 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();
|
drawScoreboard();
|
||||||
}
|
}
|
||||||
@@ -663,11 +663,11 @@ void GameScene::drawInitHudState() {
|
|||||||
Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboard(), score_progress);
|
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();
|
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();
|
ships_[1].draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -743,7 +743,7 @@ void GameScene::tocado(uint8_t player_id) {
|
|||||||
SHIP_POS,
|
SHIP_POS,
|
||||||
SHIP_ANGLE,
|
SHIP_ANGLE,
|
||||||
1.0F,
|
1.0F,
|
||||||
Defaults::Physics::Debris::VELOCITAT_BASE,
|
Defaults::Physics::Debris::SPEED_BASE,
|
||||||
SHIP_BRIGHT,
|
SHIP_BRIGHT,
|
||||||
INHERITED_VEL,
|
INHERITED_VEL,
|
||||||
0.0F, // sense herència angular
|
0.0F, // sense herència angular
|
||||||
@@ -769,20 +769,20 @@ void GameScene::drawScoreboard() {
|
|||||||
const float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE;
|
const float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE;
|
||||||
const float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING;
|
const float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING;
|
||||||
|
|
||||||
// Calcular centro de la zona del marcador
|
// Calcular centro de la zone del marcador
|
||||||
const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD;
|
const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD;
|
||||||
float centre_x = scoreboard_zone.w / 2.0F;
|
float center_x = scoreboard_zone.w / 2.0F;
|
||||||
float centre_y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F);
|
float center_y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F);
|
||||||
|
|
||||||
// Renderizar centrat
|
// 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 {
|
auto GameScene::buildScoreboard() const -> std::string {
|
||||||
// Puntuación P1 (6 dígits) - mostrar zeros si inactiu
|
// Puntuación P1 (6 dígits) - mostrar zeros si inactiu
|
||||||
std::string score_p1;
|
std::string score_p1;
|
||||||
std::string vides_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::to_string(score_per_player_[0]);
|
||||||
score_p1 = std::string(6 - std::min(6, static_cast<int>(score_p1.length())), '0') + score_p1;
|
score_p1 = std::string(6 - std::min(6, static_cast<int>(score_p1.length())), '0') + score_p1;
|
||||||
vides_p1 = (lives_per_player_[0] < 10)
|
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
|
// Puntuación P2 (6 dígits) - mostrar zeros si inactiu
|
||||||
std::string score_p2;
|
std::string score_p2;
|
||||||
std::string vides_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::to_string(score_per_player_[1]);
|
||||||
score_p2 = std::string(6 - std::min(6, static_cast<int>(score_p2.length())), '0') + score_p2;
|
score_p2 = std::string(6 - std::min(6, static_cast<int>(score_p2.length())), '0') + score_p2;
|
||||||
vides_p2 = (lives_per_player_[1] < 10)
|
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 {
|
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;
|
float x_ratio;
|
||||||
if (match_config_.isSinglePlayer()) {
|
if (match_config_.isSinglePlayer()) {
|
||||||
@@ -904,8 +904,8 @@ auto GameScene::getSpawnPoint(uint8_t player_id) const -> Vec2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
.x = zona.x + (zona.w * x_ratio),
|
.x = zone.x + (zone.w * x_ratio),
|
||||||
.y = zona.y + (zona.h * Defaults::Game::SPAWN_Y_RATIO)};
|
.y = zone.y + (zone.h * Defaults::Game::SPAWN_Y_RATIO)};
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::fireBullet(uint8_t player_id) {
|
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 sin_a = std::sin(ship_angle);
|
||||||
float tip_x = (LOCAL_TIP_X * cos_a) - (LOCAL_TIP_Y * sin_a) + ship_centre.x;
|
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;
|
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.
|
// 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]).
|
// 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_BALES;
|
constexpr int SLOTS_PER_PLAYER = Defaults::Entities::MAX_BULLETS;
|
||||||
const int START_IDX = player_id * SLOTS_PER_PLAYER;
|
const int START_IDX = player_id * SLOTS_PER_PLAYER;
|
||||||
for (int i = START_IDX; i < START_IDX + SLOTS_PER_PLAYER; i++) {
|
for (int i = START_IDX; i < START_IDX + SLOTS_PER_PLAYER; i++) {
|
||||||
if (!bullets_[i].isActive()) {
|
if (!bullets_[i].isActive()) {
|
||||||
bullets_[i].disparar(posicio_dispar, ship_angle, player_id);
|
bullets_[i].fire(fire_position, ship_angle, player_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -950,10 +950,10 @@ void GameScene::drawContinue() {
|
|||||||
float escala_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_SCALE;
|
float escala_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_SCALE;
|
||||||
float y_ratio_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_Y_RATIO;
|
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);
|
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)
|
// Countdown number (using constants)
|
||||||
const std::string COUNTER_STR = std::to_string(continue_counter_);
|
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);
|
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)
|
// "CONTINUES LEFT" (conditional + using constants)
|
||||||
if (!Defaults::Game::INFINITE_CONTINUES) {
|
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);
|
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) {
|
void GameScene::joinPlayer(uint8_t player_id) {
|
||||||
// Activate player
|
// Activate player
|
||||||
if (player_id == 0) {
|
if (player_id == 0) {
|
||||||
match_config_.jugador1_actiu = true;
|
match_config_.player1_active = true;
|
||||||
} else {
|
} else {
|
||||||
match_config_.jugador2_actiu = true;
|
match_config_.player2_active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset stats
|
// Reset stats
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class GameScene final : public Scene {
|
|||||||
std::array<Enemy, Constants::MAX_ORNIS> enemies_;
|
std::array<Enemy, Constants::MAX_ORNIS> enemies_;
|
||||||
// 6 balas: P1=[0,1,2], P2=[3,4,5]. El cast a size_t evita la
|
// 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.
|
// 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)
|
std::array<float, 2> hit_timer_per_player_; // Death timers per player (seconds)
|
||||||
|
|
||||||
// Lives and game over system
|
// Lives and game over system
|
||||||
|
|||||||
@@ -20,18 +20,18 @@ using SceneManager::SceneContext;
|
|||||||
using SceneType = SceneContext::SceneType;
|
using SceneType = SceneContext::SceneType;
|
||||||
using Option = SceneContext::Option;
|
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)
|
// 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 {
|
static auto computeLetterProgress(size_t letter_index, size_t num_letters, float global_progress, float threshold) -> float {
|
||||||
if (num_letras == 0) {
|
if (num_letters == 0) {
|
||||||
return 1.0F;
|
return 1.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular time per lletra
|
// Calcular time per letter
|
||||||
float duration_per_letra = 1.0F / static_cast<float>(num_letras);
|
float duration_per_letter = 1.0F / static_cast<float>(num_letters);
|
||||||
float step = threshold * duration_per_letra;
|
float step = threshold * duration_per_letter;
|
||||||
float start = static_cast<float>(letra_index) * step;
|
float start = static_cast<float>(letter_index) * step;
|
||||||
float end = start + duration_per_letra;
|
float end = start + duration_per_letter;
|
||||||
|
|
||||||
// Interpolar progrés
|
// Interpolar progrés
|
||||||
if (global_progress < start) {
|
if (global_progress < start) {
|
||||||
@@ -46,16 +46,15 @@ static auto computeLetterProgress(size_t letra_index, size_t num_letras, float g
|
|||||||
LogoScene::LogoScene(SDLManager& sdl, SceneContext& context)
|
LogoScene::LogoScene(SDLManager& sdl, SceneContext& context)
|
||||||
: sdl_(sdl),
|
: sdl_(sdl),
|
||||||
context_(context),
|
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";
|
std::cout << "SceneType Logo: Inicialitzant...\n";
|
||||||
|
|
||||||
// Consumir opciones (LOGO no processa opciones actualment)
|
// Consumir opciones (LOGO no processa opciones actualment)
|
||||||
auto option = context_.consumeOption();
|
auto option = context_.consumeOption();
|
||||||
(void)option; // Suprimir warning
|
(void)option; // Suprimir warning
|
||||||
|
|
||||||
so_reproduit_.fill(false); // Inicialitzar seguiment de sons
|
sound_played_.fill(false); // Inicialitzar seguiment de sons
|
||||||
initLetters();
|
initLetters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +90,7 @@ void LogoScene::initLetters() {
|
|||||||
"logo/letra_s.shp"};
|
"logo/letra_s.shp"};
|
||||||
|
|
||||||
// Pas 1: Carregar todas las formes i calcular amplades
|
// Pas 1: Carregar todas las formes i calcular amplades
|
||||||
float ancho_total = 0.0F;
|
float total_width = 0.0F;
|
||||||
|
|
||||||
for (const auto& file : archivos) {
|
for (const auto& file : archivos) {
|
||||||
auto shape = ShapeLoader::load(file);
|
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
|
// per que las posicions finals coincideixin con la mida real de las lletres
|
||||||
float ancho = ancho_sin_escalar * ESCALA_FINAL;
|
float width = width_unscaled * FINAL_SCALE;
|
||||||
float offset_centre = (shape->getCenter().x - min_x) * ESCALA_FINAL;
|
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
|
{.x = 0.0F, .y = 0.0F}, // Posición es calcularà después
|
||||||
ancho,
|
width,
|
||||||
offset_centre});
|
center_offset});
|
||||||
|
|
||||||
ancho_total += ancho;
|
total_width += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pas 2: Añadir espaiat entre lletres
|
// 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)
|
// 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);
|
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;
|
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;
|
float x_actual = x_inicial;
|
||||||
|
|
||||||
for (auto& lletra : lletres_) {
|
for (auto& letter : letters_) {
|
||||||
// Posicionar el centro de la shape (shape_centre) en pantalla
|
// 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
|
// pot no estar exactament al mig del bounding box
|
||||||
lletra.position.x = x_actual + lletra.offset_centre;
|
letter.position.x = x_actual + letter.center_offset;
|
||||||
lletra.position.y = y_centre;
|
letter.position.y = y_centre;
|
||||||
|
|
||||||
// Avançar para següent lletra
|
// Avançar para següent letter
|
||||||
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
|
x_actual += letter.width + LETTER_SPACING;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "[LogoScene] " << lletres_.size()
|
std::cout << "[LogoScene] " << letters_.size()
|
||||||
<< " lletres carregades, ancho total: " << ancho_total << " px\n";
|
<< " lletres carregades, ancho total: " << total_width << " px\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogoScene::changeState(AnimationState nou_estat) {
|
void LogoScene::changeState(AnimationState nou_estat) {
|
||||||
estat_actual_ = nou_estat;
|
current_state_ = nou_estat;
|
||||||
temps_estat_actual_ = 0.0F; // Reset time
|
temps_current_state_ = 0.0F; // Reset time
|
||||||
|
|
||||||
// Inicialitzar state de explosión
|
// Inicialitzar state de explosión
|
||||||
if (nou_estat == AnimationState::EXPLOSION) {
|
if (nou_estat == AnimationState::EXPLOSION) {
|
||||||
lletra_explosio_index_ = 0;
|
letter_explosion_index_ = 0;
|
||||||
temps_des_ultima_explosio_ = 0.0F;
|
time_since_last_explosion_ = 0.0F;
|
||||||
|
|
||||||
// Generar ordre aleatori de explosions
|
// Generar ordre aleatori de explosions
|
||||||
ordre_explosio_.clear();
|
explosion_order_.clear();
|
||||||
for (size_t i = 0; i < lletres_.size(); i++) {
|
for (size_t i = 0; i < letters_.size(); i++) {
|
||||||
ordre_explosio_.push_back(i);
|
explosion_order_.push_back(i);
|
||||||
}
|
}
|
||||||
std::random_device rd;
|
std::random_device rd;
|
||||||
std::mt19937 g(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) {
|
} else if (nou_estat == AnimationState::POST_EXPLOSION) {
|
||||||
Audio::get()->playMusic("title.ogg");
|
Audio::get()->playMusic("title.ogg");
|
||||||
}
|
}
|
||||||
@@ -180,35 +179,35 @@ void LogoScene::changeState(AnimationState nou_estat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto LogoScene::allLettersComplete() const -> bool {
|
auto LogoScene::allLettersComplete() const -> bool {
|
||||||
// Cuando global_progress = 1.0, todas las lletres tenen letra_progress = 1.0
|
// Cuando global_progress = 1.0, todas las lletres tenen letter_progress = 1.0
|
||||||
return temps_estat_actual_ >= DURACIO_ZOOM;
|
return temps_current_state_ >= DURATION_ZOOM;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogoScene::updateExplosions(float delta_time) {
|
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
|
// Comprovar si es el moment de explode la següent letter
|
||||||
if (temps_des_ultima_explosio_ >= DELAY_ENTRE_EXPLOSIONS) {
|
if (time_since_last_explosion_ >= DELAY_ENTRE_EXPLOSIONS) {
|
||||||
if (lletra_explosio_index_ < lletres_.size()) {
|
if (letter_explosion_index_ < letters_.size()) {
|
||||||
// Explotar lletra actual (en ordre aleatori)
|
// Explotar letter actual (en ordre aleatori)
|
||||||
size_t index_actual = ordre_explosio_[lletra_explosio_index_];
|
size_t index_actual = explosion_order_[letter_explosion_index_];
|
||||||
const auto& lletra = lletres_[index_actual];
|
const auto& letter = letters_[index_actual];
|
||||||
|
|
||||||
debris_manager_->explode(
|
debris_manager_->explode(
|
||||||
lletra.shape, // Forma a explode
|
letter.shape, // Forma a explode
|
||||||
lletra.position, // Posición
|
letter.position, // Posición
|
||||||
0.0F, // Angle (sin rotación)
|
0.0F, // Angle (sin rotación)
|
||||||
ESCALA_FINAL, // Escala (lletres a scale final)
|
FINAL_SCALE, // Escala (lletres a scale final)
|
||||||
VELOCITAT_EXPLOSIO, // Velocidad base
|
SPEED_EXPLOSIO, // Velocidad base
|
||||||
1.0F, // Brightness màxim (per defecte)
|
1.0F, // Brightness màxim (per defecte)
|
||||||
{.x = 0.0F, .y = 0.0F} // Sin velocity (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
|
// Passar a la següent letter
|
||||||
lletra_explosio_index_++;
|
letter_explosion_index_++;
|
||||||
temps_des_ultima_explosio_ = 0.0F;
|
time_since_last_explosion_ = 0.0F;
|
||||||
} else {
|
} else {
|
||||||
// Todas las lletres han explotat, transición a POST_EXPLOSION
|
// Todas las lletres han explotat, transición a POST_EXPLOSION
|
||||||
changeState(AnimationState::POST_EXPLOSION);
|
changeState(AnimationState::POST_EXPLOSION);
|
||||||
@@ -217,31 +216,31 @@ void LogoScene::updateExplosions(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LogoScene::update(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:
|
case AnimationState::PRE_ANIMATION:
|
||||||
if (temps_estat_actual_ >= DURACIO_PRE) {
|
if (temps_current_state_ >= DURATION_PRE) {
|
||||||
changeState(AnimationState::ANIMATION);
|
changeState(AnimationState::ANIMATION);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AnimationState::ANIMATION: {
|
case AnimationState::ANIMATION: {
|
||||||
// Reproduir so per cada lletra cuando comença a aparèixer
|
// Reproduir so per cada letter cuando comença a aparèixer
|
||||||
float global_progress = std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F);
|
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++) {
|
for (size_t i = 0; i < letters_.size() && i < sound_played_.size(); i++) {
|
||||||
if (!so_reproduit_[i]) {
|
if (!sound_played_[i]) {
|
||||||
float letra_progress = computeLetterProgress(
|
float letter_progress = computeLetterProgress(
|
||||||
i,
|
i,
|
||||||
lletres_.size(),
|
letters_.size(),
|
||||||
global_progress,
|
global_progress,
|
||||||
THRESHOLD_LETRA);
|
LETTER_THRESHOLD);
|
||||||
|
|
||||||
// Reproduir so cuando la lletra comença a aparèixer (progress > 0)
|
// Reproduir so cuando la letter comença a aparèixer (progress > 0)
|
||||||
if (letra_progress > 0.0F) {
|
if (letter_progress > 0.0F) {
|
||||||
Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME);
|
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:
|
case AnimationState::POST_ANIMATION:
|
||||||
if (temps_estat_actual_ >= DURACIO_POST_ANIMATION) {
|
if (temps_current_state_ >= DURATION_POST_ANIMATION) {
|
||||||
changeState(AnimationState::EXPLOSION);
|
changeState(AnimationState::EXPLOSION);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -263,7 +262,7 @@ void LogoScene::update(float delta_time) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case AnimationState::POST_EXPLOSION:
|
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
|
// Transición a pantalla de título
|
||||||
context_.setNextScene(SceneType::TITLE);
|
context_.setNextScene(SceneType::TITLE);
|
||||||
}
|
}
|
||||||
@@ -283,47 +282,47 @@ void LogoScene::draw() {
|
|||||||
// Director ha hecho el clear; aquí solo pintamos lo de la escena.
|
// Director ha hecho el clear; aquí solo pintamos lo de la escena.
|
||||||
|
|
||||||
// PRE_ANIMATION: Solo pantalla negra (no se pinta nada).
|
// PRE_ANIMATION: Solo pantalla negra (no se pinta nada).
|
||||||
if (estat_actual_ == AnimationState::PRE_ANIMATION) {
|
if (current_state_ == AnimationState::PRE_ANIMATION) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANIMATION o POST_ANIMATION: Dibuixar lletres con animación
|
// ANIMATION o POST_ANIMATION: Dibuixar lletres con animación
|
||||||
if (estat_actual_ == AnimationState::ANIMATION ||
|
if (current_state_ == AnimationState::ANIMATION ||
|
||||||
estat_actual_ == AnimationState::POST_ANIMATION) {
|
current_state_ == AnimationState::POST_ANIMATION) {
|
||||||
float global_progress =
|
float global_progress =
|
||||||
(estat_actual_ == AnimationState::ANIMATION)
|
(current_state_ == AnimationState::ANIMATION)
|
||||||
? std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F)
|
? std::min(temps_current_state_ / DURATION_ZOOM, 1.0F)
|
||||||
: 1.0F; // POST: mantenir al 100%
|
: 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++) {
|
for (size_t i = 0; i < letters_.size(); i++) {
|
||||||
const auto& lletra = lletres_[i];
|
const auto& letter = letters_[i];
|
||||||
|
|
||||||
float letra_progress = computeLetterProgress(
|
float letter_progress = computeLetterProgress(
|
||||||
i,
|
i,
|
||||||
lletres_.size(),
|
letters_.size(),
|
||||||
global_progress,
|
global_progress,
|
||||||
THRESHOLD_LETRA);
|
LETTER_THRESHOLD);
|
||||||
|
|
||||||
if (letra_progress <= 0.0F) {
|
if (letter_progress <= 0.0F) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2 pos_actual;
|
Vec2 pos_actual;
|
||||||
pos_actual.x =
|
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 =
|
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 ease_factor = 1.0F - ((1.0F - t) * (1.0F - t));
|
||||||
float current_scale =
|
float current_scale =
|
||||||
ESCALA_INICIAL + ((ESCALA_FINAL - ESCALA_INICIAL) * ease_factor);
|
INITIAL_SCALE + ((FINAL_SCALE - INITIAL_SCALE) * ease_factor);
|
||||||
|
|
||||||
Rendering::renderShape(
|
Rendering::renderShape(
|
||||||
sdl_.getRenderer(),
|
sdl_.getRenderer(),
|
||||||
lletra.shape,
|
letter.shape,
|
||||||
pos_actual,
|
pos_actual,
|
||||||
0.0F,
|
0.0F,
|
||||||
current_scale,
|
current_scale,
|
||||||
@@ -332,24 +331,24 @@ void LogoScene::draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EXPLOSION: Dibuixar solo lletres que aún no han explotat
|
// 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
|
// Crear conjunt de lletres ya explotades
|
||||||
std::set<size_t> explotades;
|
std::set<size_t> explotades;
|
||||||
for (size_t i = 0; i < lletra_explosio_index_; i++) {
|
for (size_t i = 0; i < letter_explosion_index_; i++) {
|
||||||
explotades.insert(ordre_explosio_[i]);
|
explotades.insert(explosion_order_[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuixar solo lletres que NO han explotat
|
// 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)) {
|
if (!explotades.contains(i)) {
|
||||||
const auto& lletra = lletres_[i];
|
const auto& letter = letters_[i];
|
||||||
|
|
||||||
Rendering::renderShape(
|
Rendering::renderShape(
|
||||||
sdl_.getRenderer(),
|
sdl_.getRenderer(),
|
||||||
lletra.shape,
|
letter.shape,
|
||||||
lletra.position,
|
letter.position,
|
||||||
0.0F,
|
0.0F,
|
||||||
ESCALA_FINAL,
|
FINAL_SCALE,
|
||||||
1.0F);
|
1.0F);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,76 +20,76 @@
|
|||||||
#include "game/effects/debris_manager.hpp"
|
#include "game/effects/debris_manager.hpp"
|
||||||
|
|
||||||
class LogoScene final : public Scene {
|
class LogoScene final : public Scene {
|
||||||
public:
|
public:
|
||||||
explicit LogoScene(SDLManager& sdl, SceneManager::SceneContext& context);
|
explicit LogoScene(SDLManager& sdl, SceneManager::SceneContext& context);
|
||||||
~LogoScene() override; // Destructor per aturar sons
|
~LogoScene() override; // Destructor per aturar sons
|
||||||
|
|
||||||
// Scene interface
|
// Scene interface
|
||||||
void handleEvent(const SDL_Event& event) override;
|
void handleEvent(const SDL_Event& event) override;
|
||||||
void update(float delta_time) override;
|
void update(float delta_time) override;
|
||||||
void draw() override;
|
void draw() override;
|
||||||
[[nodiscard]] auto isFinished() const -> bool override;
|
[[nodiscard]] auto isFinished() const -> bool override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Màquina de estats per l'animación
|
// Màquina de estats per l'animación
|
||||||
enum class AnimationState : std::uint8_t {
|
enum class AnimationState : std::uint8_t {
|
||||||
PRE_ANIMATION, // Pantalla negra inicial
|
PRE_ANIMATION, // Pantalla negra inicial
|
||||||
ANIMATION, // Animación de zoom de lletres
|
ANIMATION, // Animación de zoom de lletres
|
||||||
POST_ANIMATION, // Logo complet visible
|
POST_ANIMATION, // Logo complet visible
|
||||||
EXPLOSION, // Explosión seqüencial de lletres
|
EXPLOSION, // Explosión seqüencial de lletres
|
||||||
POST_EXPLOSION // Espera después de l'última explosión
|
POST_EXPLOSION // Espera después de l'última explosión
|
||||||
};
|
};
|
||||||
|
|
||||||
SDLManager& sdl_;
|
SDLManager& sdl_;
|
||||||
SceneManager::SceneContext& context_;
|
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
|
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
|
// Gestor de fragments de explosions
|
||||||
std::unique_ptr<Effects::DebrisManager> debris_manager_;
|
std::unique_ptr<Effects::DebrisManager> debris_manager_;
|
||||||
|
|
||||||
// Seguiment de explosions seqüencials
|
// Seguiment de explosions seqüencials
|
||||||
size_t lletra_explosio_index_{0}; // Índex de la següent lletra a explode
|
size_t letter_explosion_index_{0}; // Índex de la següent letter a explode
|
||||||
float temps_des_ultima_explosio_{0.0F}; // Temps desde l'última explosión
|
float time_since_last_explosion_{0.0F}; // Temps desde l'última explosión
|
||||||
std::vector<size_t> ordre_explosio_; // Ordre aleatori de índexs de lletres
|
std::vector<size_t> explosion_order_; // Ordre aleatori de índexs de lletres
|
||||||
|
|
||||||
// Estructura para cada lletra del logo
|
// Estructura para cada letter del logo
|
||||||
struct LetraLogo {
|
struct LogoLetter {
|
||||||
std::shared_ptr<Graphics::Shape> shape;
|
std::shared_ptr<Graphics::Shape> shape;
|
||||||
Vec2 position; // Posición final en pantalla
|
Vec2 position; // Posición final en pantalla
|
||||||
float ancho; // Ancho del bounding box
|
float width; // Ancho del bounding box
|
||||||
float offset_centre; // Distancia de min_x a shape_centre.x
|
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)
|
// 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
|
// Constants de animación
|
||||||
static constexpr float DURACIO_PRE = 1.5F; // Duració PRE_ANIMATION (pantalla negra)
|
static constexpr float DURATION_PRE = 1.5F; // Duració PRE_ANIMATION (pantalla negra)
|
||||||
static constexpr float DURACIO_ZOOM = 4.0F; // Duració del zoom (segons)
|
static constexpr float DURATION_ZOOM = 4.0F; // Duració del zoom (segons)
|
||||||
static constexpr float DURACIO_POST_ANIMATION = 3.0F; // Duració POST_ANIMATION (logo complet)
|
static constexpr float DURATION_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_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 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 SPEED_EXPLOSIO = 240.0F; // Velocidad base fragments (px/s)
|
||||||
static constexpr float ESCALA_INICIAL = 0.1F; // Escala inicial (10%)
|
static constexpr float INITIAL_SCALE = 0.1F; // Escala inicial (10%)
|
||||||
static constexpr float ESCALA_FINAL = 0.8F; // Escala final (80%)
|
static constexpr float FINAL_SCALE = 0.8F; // Escala final (80%)
|
||||||
static constexpr float ESPAI_ENTRE_LLETRES = 10.0F; // Espaiat entre lletres
|
static constexpr float LETTER_SPACING = 10.0F; // Espaiat entre lletres
|
||||||
|
|
||||||
// Constants de animación seqüencial
|
// 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 LETTER_THRESHOLD = 0.6F; // Umbral per activar següent letter (0.0-1.0)
|
||||||
static constexpr float ORIGEN_ZOOM_X = Defaults::Game::WIDTH * 0.5F; // Vec2 inicial X del zoom
|
static constexpr float ZOOM_ORIGIN_X = Defaults::Game::WIDTH * 0.5F; // Vec2 inicial X del zoom
|
||||||
static constexpr float ORIGEN_ZOOM_Y = Defaults::Game::HEIGHT * 0.4F; // Vec2 inicial Y del zoom
|
static constexpr float ZOOM_ORIGIN_Y = Defaults::Game::HEIGHT * 0.4F; // Vec2 inicial Y del zoom
|
||||||
|
|
||||||
// Métodos privats
|
// Métodos privats
|
||||||
void initLetters();
|
void initLetters();
|
||||||
void updateExplosions(float delta_time);
|
void updateExplosions(float delta_time);
|
||||||
// Estático: solo consulta Input (singleton), no estado de la escena.
|
// Estático: solo consulta Input (singleton), no estado de la escena.
|
||||||
static auto checkSkipButtonPressed() -> bool;
|
static auto checkSkipButtonPressed() -> bool;
|
||||||
|
|
||||||
// Métodos de gestió de estats
|
// Métodos de gestió de estats
|
||||||
void changeState(AnimationState nou_estat);
|
void changeState(AnimationState nou_estat);
|
||||||
[[nodiscard]] auto allLettersComplete() const -> bool;
|
[[nodiscard]] auto allLettersComplete() const -> bool;
|
||||||
};
|
};
|
||||||
|
|||||||
+139
-139
@@ -36,15 +36,15 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
|
|||||||
text_(sdl.getRenderer()) {
|
text_(sdl.getRenderer()) {
|
||||||
std::cout << "SceneType Titol: Inicialitzant...\n";
|
std::cout << "SceneType Titol: Inicialitzant...\n";
|
||||||
|
|
||||||
match_config_.jugador1_actiu = false;
|
match_config_.player1_active = false;
|
||||||
match_config_.jugador2_actiu = false;
|
match_config_.player2_active = false;
|
||||||
match_config_.mode = GameConfig::Mode::NORMAL;
|
match_config_.mode = GameConfig::Mode::NORMAL;
|
||||||
|
|
||||||
auto option = context_.consumeOption();
|
auto option = context_.consumeOption();
|
||||||
if (option == Option::JUMP_TO_TITLE_MAIN) {
|
if (option == Option::JUMP_TO_TITLE_MAIN) {
|
||||||
std::cout << "SceneType Titol: Opció JUMP_TO_TITLE_MAIN activada\n";
|
std::cout << "SceneType Titol: Opció JUMP_TO_TITLE_MAIN activada\n";
|
||||||
estat_actual_ = TitleState::MAIN;
|
current_state_ = TitleState::MAIN;
|
||||||
temps_estat_main_ = 0.0F;
|
state_time_main_ = 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Càmera 3D: posicionada a l'origen, mirant cap a +Z, amb Y cap amunt.
|
// 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(),
|
camera_.get(),
|
||||||
200);
|
200);
|
||||||
starfield_->setColor(Defaults::Title::Colors::STARFIELD);
|
starfield_->setColor(Defaults::Title::Colors::STARFIELD);
|
||||||
if (estat_actual_ == TitleState::MAIN) {
|
if (current_state_ == TitleState::MAIN) {
|
||||||
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
||||||
} else {
|
} else {
|
||||||
starfield_->setBrightness(0.0F);
|
starfield_->setBrightness(0.0F);
|
||||||
@@ -103,7 +103,7 @@ void TitleScene::initTitle() {
|
|||||||
"title/letra_n.shp",
|
"title/letra_n.shp",
|
||||||
"title/letra_i.shp"};
|
"title/letra_i.shp"};
|
||||||
|
|
||||||
float ancho_total_orni = 0.0F;
|
float total_width_orni = 0.0F;
|
||||||
for (const auto& file : FITXERS_ORNI) {
|
for (const auto& file : FITXERS_ORNI) {
|
||||||
auto shape = ShapeLoader::load(file);
|
auto shape = ShapeLoader::load(file);
|
||||||
if (!shape || !shape->isValid()) {
|
if (!shape || !shape->isValid()) {
|
||||||
@@ -122,25 +122,25 @@ void TitleScene::initTitle() {
|
|||||||
max_y = std::max(max_y, point.y);
|
max_y = std::max(max_y, point.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const float ANCHO = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
const float WIDTH = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
||||||
const float ALTURA = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
|
const float HEIGHT = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
|
||||||
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
const float CENTER_OFFSET = (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});
|
letters_orni_.push_back({shape, {.x = 0.0F, .y = 0.0F}, WIDTH, HEIGHT, CENTER_OFFSET});
|
||||||
ancho_total_orni += ANCHO;
|
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;
|
float x_actual = (Defaults::Game::WIDTH - total_width_orni) / 2.0F;
|
||||||
for (auto& lletra : lletres_orni_) {
|
for (auto& letter : letters_orni_) {
|
||||||
lletra.position.x = x_actual + lletra.offset_centre;
|
letter.position.x = x_actual + letter.center_offset;
|
||||||
lletra.position.y = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
|
letter.position.y = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
|
||||||
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
|
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 Y_ORNI = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
|
||||||
const float SEPARACION = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_LINE_SPACING;
|
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 = {
|
const std::vector<std::string> FITXERS_ATTACK = {
|
||||||
"title/letra_a.shp",
|
"title/letra_a.shp",
|
||||||
@@ -151,7 +151,7 @@ void TitleScene::initTitle() {
|
|||||||
"title/letra_k.shp",
|
"title/letra_k.shp",
|
||||||
"title/letra_exclamacion.shp"};
|
"title/letra_exclamacion.shp"};
|
||||||
|
|
||||||
float ancho_total_attack = 0.0F;
|
float total_width_attack = 0.0F;
|
||||||
for (const auto& file : FITXERS_ATTACK) {
|
for (const auto& file : FITXERS_ATTACK) {
|
||||||
auto shape = ShapeLoader::load(file);
|
auto shape = ShapeLoader::load(file);
|
||||||
if (!shape || !shape->isValid()) {
|
if (!shape || !shape->isValid()) {
|
||||||
@@ -170,28 +170,28 @@ void TitleScene::initTitle() {
|
|||||||
max_y = std::max(max_y, point.y);
|
max_y = std::max(max_y, point.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const float ANCHO = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
const float WIDTH = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
||||||
const float ALTURA = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
|
const float HEIGHT = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
|
||||||
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
const float CENTER_OFFSET = (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});
|
letters_attack_.push_back({shape, {.x = 0.0F, .y = 0.0F}, WIDTH, HEIGHT, CENTER_OFFSET});
|
||||||
ancho_total_attack += ANCHO;
|
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;
|
x_actual = (Defaults::Game::WIDTH - total_width_attack) / 2.0F;
|
||||||
for (auto& lletra : lletres_attack_) {
|
for (auto& letter : letters_attack_) {
|
||||||
lletra.position.x = x_actual + lletra.offset_centre;
|
letter.position.x = x_actual + letter.center_offset;
|
||||||
lletra.position.y = y_attack_dinamica_;
|
letter.position.y = dynamic_attack_y_;
|
||||||
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
|
x_actual += letter.width + LETTER_SPACING;
|
||||||
}
|
}
|
||||||
|
|
||||||
posicions_originals_orni_.clear();
|
original_positions_orni_.clear();
|
||||||
for (const auto& lletra : lletres_orni_) {
|
for (const auto& letter : letters_orni_) {
|
||||||
posicions_originals_orni_.push_back(lletra.position);
|
original_positions_orni_.push_back(letter.position);
|
||||||
}
|
}
|
||||||
posicions_originals_attack_.clear();
|
original_positions_attack_.clear();
|
||||||
for (const auto& lletra : lletres_attack_) {
|
for (const auto& letter : letters_attack_) {
|
||||||
posicions_originals_attack_.push_back(lletra.position);
|
original_positions_attack_.push_back(letter.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,8 +211,8 @@ void TitleScene::inicialitzarJailgames() {
|
|||||||
|
|
||||||
constexpr float SCALE = Defaults::Title::Layout::JAILGAMES_SCALE;
|
constexpr float SCALE = Defaults::Title::Layout::JAILGAMES_SCALE;
|
||||||
|
|
||||||
float ancho_total = 0.0F;
|
float total_width = 0.0F;
|
||||||
float altura_max = 0.0F;
|
float max_height = 0.0F;
|
||||||
for (const auto& file : FITXERS) {
|
for (const auto& file : FITXERS) {
|
||||||
auto shape = ShapeLoader::load(file);
|
auto shape = ShapeLoader::load(file);
|
||||||
if (!shape || !shape->isValid()) {
|
if (!shape || !shape->isValid()) {
|
||||||
@@ -231,28 +231,28 @@ void TitleScene::inicialitzarJailgames() {
|
|||||||
max_y = std::max(max_y, point.y);
|
max_y = std::max(max_y, point.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const float ANCHO = (max_x - min_x) * SCALE;
|
const float WIDTH = (max_x - min_x) * SCALE;
|
||||||
const float ALTURA = (max_y - min_y) * SCALE;
|
const float HEIGHT = (max_y - min_y) * SCALE;
|
||||||
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * SCALE;
|
const float CENTER_OFFSET = (shape->getCenter().x - min_x) * SCALE;
|
||||||
lletres_jailgames_.push_back({shape, {.x = 0.0F, .y = 0.0F}, ANCHO, ALTURA, OFFSET_CENTRE});
|
letters_jailgames_.push_back({shape, {.x = 0.0F, .y = 0.0F}, WIDTH, HEIGHT, CENTER_OFFSET});
|
||||||
ancho_total += ANCHO;
|
total_width += WIDTH;
|
||||||
altura_max = std::max(altura_max, ALTURA);
|
max_height = std::max(max_height, HEIGHT);
|
||||||
}
|
}
|
||||||
constexpr float ESPAI_JAILGAMES = ESPAI_ENTRE_LLETRES * SCALE;
|
constexpr float JAILGAMES_SPACING = LETTER_SPACING * SCALE;
|
||||||
if (!lletres_jailgames_.empty()) {
|
if (!letters_jailgames_.empty()) {
|
||||||
ancho_total += ESPAI_JAILGAMES * static_cast<float>(lletres_jailgames_.size() - 1);
|
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 Y_COPY = Defaults::Game::HEIGHT * Defaults::Title::Layout::COPYRIGHT1_POS;
|
||||||
const float GAP = Defaults::Game::HEIGHT * Defaults::Title::Layout::JAILGAMES_COPYRIGHT_GAP;
|
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 Y_CENTRE = Y_COPY - GAP - (max_height / 2.0F);
|
||||||
const float X_INICIAL = (Defaults::Game::WIDTH - ancho_total) / 2.0F;
|
const float X_INICIAL = (Defaults::Game::WIDTH - total_width) / 2.0F;
|
||||||
|
|
||||||
float x_actual = X_INICIAL;
|
float x_actual = X_INICIAL;
|
||||||
for (auto& lletra : lletres_jailgames_) {
|
for (auto& letter : letters_jailgames_) {
|
||||||
lletra.position.x = x_actual + lletra.offset_centre;
|
letter.position.x = x_actual + letter.center_offset;
|
||||||
lletra.position.y = Y_CENTRE;
|
letter.position.y = Y_CENTRE;
|
||||||
x_actual += lletra.ancho + ESPAI_JAILGAMES;
|
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 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;
|
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{
|
const Vec2 POS{
|
||||||
.x = SCREEN_CENTRE_X + (JAILGAMES_S * (lletra.position.x - SCREEN_CENTRE_X)),
|
.x = SCREEN_CENTRE_X + (JAILGAMES_S * (letter.position.x - SCREEN_CENTRE_X)),
|
||||||
.y = SCREEN_CENTRE_Y + (JAILGAMES_S * (lletra.position.y - SCREEN_CENTRE_Y)),
|
.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;
|
std::string copyright = Project::COPYRIGHT;
|
||||||
for (char& c : copyright) {
|
for (char& c : copyright) {
|
||||||
@@ -297,15 +297,15 @@ void TitleScene::update(float delta_time) {
|
|||||||
starfield_->update(delta_time);
|
starfield_->update(delta_time);
|
||||||
}
|
}
|
||||||
if (ship_animator_ &&
|
if (ship_animator_ &&
|
||||||
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
|
(current_state_ == TitleState::STARFIELD_FADE_IN ||
|
||||||
estat_actual_ == TitleState::STARFIELD ||
|
current_state_ == TitleState::STARFIELD ||
|
||||||
estat_actual_ == TitleState::MAIN ||
|
current_state_ == TitleState::MAIN ||
|
||||||
estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) {
|
current_state_ == TitleState::PLAYER_JOIN_PHASE)) {
|
||||||
ship_animator_->update(delta_time);
|
ship_animator_->update(delta_time);
|
||||||
}
|
}
|
||||||
updateFlashes(delta_time);
|
updateFlashes(delta_time);
|
||||||
|
|
||||||
switch (estat_actual_) {
|
switch (current_state_) {
|
||||||
case TitleState::STARFIELD_FADE_IN:
|
case TitleState::STARFIELD_FADE_IN:
|
||||||
updateStarfieldFadeInState(delta_time);
|
updateStarfieldFadeInState(delta_time);
|
||||||
break;
|
break;
|
||||||
@@ -329,10 +329,10 @@ void TitleScene::update(float delta_time) {
|
|||||||
|
|
||||||
void TitleScene::updateStarfieldFadeInState(float delta_time) {
|
void TitleScene::updateStarfieldFadeInState(float delta_time) {
|
||||||
temps_acumulat_ += 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);
|
starfield_->setBrightness(PROGRESS * BRIGHTNESS_STARFIELD);
|
||||||
if (temps_acumulat_ >= DURACIO_FADE_IN) {
|
if (temps_acumulat_ >= DURATION_FADE_IN) {
|
||||||
estat_actual_ = TitleState::STARFIELD;
|
current_state_ = TitleState::STARFIELD;
|
||||||
temps_acumulat_ = 0.0F;
|
temps_acumulat_ = 0.0F;
|
||||||
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
||||||
}
|
}
|
||||||
@@ -340,16 +340,16 @@ void TitleScene::updateStarfieldFadeInState(float delta_time) {
|
|||||||
|
|
||||||
void TitleScene::updateStarfieldState(float delta_time) {
|
void TitleScene::updateStarfieldState(float delta_time) {
|
||||||
temps_acumulat_ += delta_time;
|
temps_acumulat_ += delta_time;
|
||||||
if (temps_acumulat_ >= DURACIO_INIT) {
|
if (temps_acumulat_ >= DURATION_INIT) {
|
||||||
estat_actual_ = TitleState::MAIN;
|
current_state_ = TitleState::MAIN;
|
||||||
temps_estat_main_ = 0.0F;
|
state_time_main_ = 0.0F;
|
||||||
animacio_activa_ = false;
|
animation_active_ = false;
|
||||||
factor_lerp_ = 0.0F;
|
lerp_factor_ = 0.0F;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScene::updateMainState(float delta_time) {
|
void TitleScene::updateMainState(float delta_time) {
|
||||||
temps_estat_main_ += delta_time;
|
state_time_main_ += delta_time;
|
||||||
|
|
||||||
namespace S = Defaults::Title::Sequence;
|
namespace S = Defaults::Title::Sequence;
|
||||||
namespace Sh = Defaults::Title::Ships;
|
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_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;
|
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(
|
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,
|
0.0F,
|
||||||
1.0F);
|
1.0F);
|
||||||
intro_copyright_progress_ = std::clamp(
|
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,
|
0.0F,
|
||||||
1.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_ != nullptr) {
|
||||||
ship_animator_->setVisible(true);
|
ship_animator_->setVisible(true);
|
||||||
ship_animator_->startEntryAnimation();
|
ship_animator_->startEntryAnimation();
|
||||||
ships_intro_launched_ = true;
|
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;
|
press_start_visible_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// L'oscil·lació suau del logo arrenca quan el logo ha aterrat. Així
|
// L'oscil·lació suau del logo arrenca quan el logo ha aterrat. Així
|
||||||
// l'amplitud creix gradualment (lerp) durant la resta de la intro.
|
// l'amplitud creix gradualment (lerp) durant la resta de la intro.
|
||||||
if (temps_estat_main_ < S::LOGO_ENTRY_DURATION) {
|
if (state_time_main_ < S::LOGO_ENTRY_DURATION) {
|
||||||
factor_lerp_ = 0.0F;
|
lerp_factor_ = 0.0F;
|
||||||
animacio_activa_ = false;
|
animation_active_ = false;
|
||||||
} else if (temps_estat_main_ < S::LOGO_ENTRY_DURATION + DURACIO_LERP) {
|
} else if (state_time_main_ < S::LOGO_ENTRY_DURATION + DURATION_LERP) {
|
||||||
factor_lerp_ = (temps_estat_main_ - S::LOGO_ENTRY_DURATION) / DURACIO_LERP;
|
lerp_factor_ = (state_time_main_ - S::LOGO_ENTRY_DURATION) / DURATION_LERP;
|
||||||
animacio_activa_ = true;
|
animation_active_ = true;
|
||||||
} else {
|
} else {
|
||||||
factor_lerp_ = 1.0F;
|
lerp_factor_ = 1.0F;
|
||||||
animacio_activa_ = true;
|
animation_active_ = true;
|
||||||
}
|
}
|
||||||
updateLogoAnimation(delta_time);
|
updateLogoAnimation(delta_time);
|
||||||
}
|
}
|
||||||
@@ -404,8 +404,8 @@ void TitleScene::updatePlayerJoinPhaseState(float delta_time) {
|
|||||||
temps_acumulat_ += delta_time;
|
temps_acumulat_ += delta_time;
|
||||||
updateLogoAnimation(delta_time);
|
updateLogoAnimation(delta_time);
|
||||||
|
|
||||||
const bool P1_ABANS = match_config_.jugador1_actiu;
|
const bool P1_ABANS = match_config_.player1_active;
|
||||||
const bool P2_ABANS = match_config_.jugador2_actiu;
|
const bool P2_ABANS = match_config_.player2_active;
|
||||||
|
|
||||||
if (checkStartGameButtonPressed()) {
|
if (checkStartGameButtonPressed()) {
|
||||||
context_.setMatchConfig(match_config_);
|
context_.setMatchConfig(match_config_);
|
||||||
@@ -414,27 +414,27 @@ void TitleScene::updatePlayerJoinPhaseState(float delta_time) {
|
|||||||
temps_acumulat_ = 0.0F;
|
temps_acumulat_ = 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (temps_acumulat_ >= DURACIO_TRANSITION) {
|
if (temps_acumulat_ >= DURATION_TRANSITION) {
|
||||||
estat_actual_ = TitleState::BLACK_SCREEN;
|
current_state_ = TitleState::BLACK_SCREEN;
|
||||||
temps_acumulat_ = 0.0F;
|
temps_acumulat_ = 0.0F;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScene::updateBlackScreenState(float delta_time) {
|
void TitleScene::updateBlackScreenState(float delta_time) {
|
||||||
temps_acumulat_ += delta_time;
|
temps_acumulat_ += delta_time;
|
||||||
if (temps_acumulat_ >= DURACIO_BLACK_SCREEN) {
|
if (temps_acumulat_ >= DURATION_BLACK_SCREEN) {
|
||||||
context_.setNextScene(SceneType::GAME);
|
context_.setNextScene(SceneType::GAME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScene::handleSkipInput() {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (!checkSkipButtonPressed()) {
|
if (!checkSkipButtonPressed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
estat_actual_ = TitleState::MAIN;
|
current_state_ = TitleState::MAIN;
|
||||||
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
||||||
|
|
||||||
// Saltar la intro coreografiada: deixar tots els elements ja in-place.
|
// 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);
|
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 +
|
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;
|
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_logo_progress_ = 1.0F;
|
||||||
intro_jailgames_progress_ = 1.0F;
|
intro_jailgames_progress_ = 1.0F;
|
||||||
intro_copyright_progress_ = 1.0F;
|
intro_copyright_progress_ = 1.0F;
|
||||||
@@ -458,7 +458,7 @@ void TitleScene::handleSkipInput() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TitleScene::handleStartInput() {
|
void TitleScene::handleStartInput() {
|
||||||
if (estat_actual_ != TitleState::MAIN) {
|
if (current_state_ != TitleState::MAIN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// No acceptar START fins que la intro coreografiada haja conclòs i el
|
// No acceptar START fins que la intro coreografiada haja conclòs i el
|
||||||
@@ -466,8 +466,8 @@ void TitleScene::handleStartInput() {
|
|||||||
if (!press_start_visible_) {
|
if (!press_start_visible_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool P1_ABANS = match_config_.jugador1_actiu;
|
const bool P1_ABANS = match_config_.player1_active;
|
||||||
const bool P2_ABANS = match_config_.jugador2_actiu;
|
const bool P2_ABANS = match_config_.player2_active;
|
||||||
|
|
||||||
if (!checkStartGameButtonPressed()) {
|
if (!checkStartGameButtonPressed()) {
|
||||||
return;
|
return;
|
||||||
@@ -479,7 +479,7 @@ void TitleScene::handleStartInput() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context_.setMatchConfig(match_config_);
|
context_.setMatchConfig(match_config_);
|
||||||
estat_actual_ = TitleState::PLAYER_JOIN_PHASE;
|
current_state_ = TitleState::PLAYER_JOIN_PHASE;
|
||||||
temps_acumulat_ = 0.0F;
|
temps_acumulat_ = 0.0F;
|
||||||
|
|
||||||
triggerExitForJoinedPlayers(P1_ABANS, P2_ABANS, "");
|
triggerExitForJoinedPlayers(P1_ABANS, P2_ABANS, "");
|
||||||
@@ -492,55 +492,55 @@ void TitleScene::triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_act
|
|||||||
if (ship_animator_ == nullptr) {
|
if (ship_animator_ == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (match_config_.jugador1_actiu && !p1_was_active) {
|
if (match_config_.player1_active && !p1_was_active) {
|
||||||
ship_animator_->triggerExitAnimationForPlayer(1);
|
ship_animator_->triggerExitAnimationForPlayer(1);
|
||||||
std::cout << "[TitleScene] P1 " << log_prefix << "ship exiting\n";
|
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);
|
ship_animator_->triggerExitAnimationForPlayer(2);
|
||||||
std::cout << "[TitleScene] P2 " << log_prefix << "ship exiting\n";
|
std::cout << "[TitleScene] P2 " << log_prefix << "ship exiting\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScene::updateLogoAnimation(float delta_time) {
|
void TitleScene::updateLogoAnimation(float delta_time) {
|
||||||
if (!animacio_activa_) {
|
if (!animation_active_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
temps_animacio_ += delta_time * factor_lerp_;
|
animation_time_ += delta_time * lerp_factor_;
|
||||||
|
|
||||||
const float TWO_PI = 2.0F * Defaults::Math::PI;
|
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_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 * temps_animacio_) + ORBIT_PHASE_OFFSET);
|
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) {
|
for (std::size_t i = 0; i < letters_orni_.size(); ++i) {
|
||||||
lletres_orni_[i].position.x = posicions_originals_orni_[i].x + std::round(OFFSET_X);
|
letters_orni_[i].position.x = original_positions_orni_[i].x + std::round(OFFSET_X);
|
||||||
lletres_orni_[i].position.y = posicions_originals_orni_[i].y + std::round(OFFSET_Y);
|
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) {
|
for (std::size_t i = 0; i < letters_attack_.size(); ++i) {
|
||||||
lletres_attack_[i].position.x = posicions_originals_attack_[i].x + std::round(OFFSET_X);
|
letters_attack_[i].position.x = original_positions_attack_[i].x + std::round(OFFSET_X);
|
||||||
lletres_attack_[i].position.y = posicions_originals_attack_[i].y + std::round(OFFSET_Y);
|
letters_attack_[i].position.y = original_positions_attack_[i].y + std::round(OFFSET_Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScene::draw() {
|
void TitleScene::draw() {
|
||||||
if (starfield_ && estat_actual_ != TitleState::BLACK_SCREEN) {
|
if (starfield_ && current_state_ != TitleState::BLACK_SCREEN) {
|
||||||
starfield_->draw();
|
starfield_->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ship_animator_ &&
|
if (ship_animator_ &&
|
||||||
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
|
(current_state_ == TitleState::STARFIELD_FADE_IN ||
|
||||||
estat_actual_ == TitleState::STARFIELD ||
|
current_state_ == TitleState::STARFIELD ||
|
||||||
estat_actual_ == TitleState::MAIN ||
|
current_state_ == TitleState::MAIN ||
|
||||||
estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) {
|
current_state_ == TitleState::PLAYER_JOIN_PHASE)) {
|
||||||
ship_animator_->draw();
|
ship_animator_->draw();
|
||||||
}
|
}
|
||||||
drawFlashes();
|
drawFlashes();
|
||||||
|
|
||||||
if (estat_actual_ == TitleState::STARFIELD_FADE_IN || estat_actual_ == TitleState::STARFIELD) {
|
if (current_state_ == TitleState::STARFIELD_FADE_IN || current_state_ == TitleState::STARFIELD) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (estat_actual_ != TitleState::MAIN && estat_actual_ != TitleState::PLAYER_JOIN_PHASE) {
|
if (current_state_ != TitleState::MAIN && current_state_ != TitleState::PLAYER_JOIN_PHASE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,52 +552,52 @@ void TitleScene::draw() {
|
|||||||
const float SCREEN_CENTRE_Y = Defaults::Game::HEIGHT / 2.0F;
|
const float SCREEN_CENTRE_Y = Defaults::Game::HEIGHT / 2.0F;
|
||||||
const float LOGO_RENDER_SCALE = Defaults::Title::Layout::LOGO_SCALE * LOGO_S;
|
const float LOGO_RENDER_SCALE = Defaults::Title::Layout::LOGO_SCALE * LOGO_S;
|
||||||
|
|
||||||
if (animacio_activa_) {
|
if (animation_active_) {
|
||||||
float temps_shadow = std::max(0.0F, temps_animacio_ - SHADOW_DELAY);
|
float temps_shadow = std::max(0.0F, animation_time_ - SHADOW_DELAY);
|
||||||
const float TWO_PI = 2.0F * Defaults::Math::PI;
|
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_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;
|
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) {
|
for (std::size_t i = 0; i < letters_orni_.size(); ++i) {
|
||||||
const float BASE_X = posicions_originals_orni_[i].x + std::round(SHADOW_OX);
|
const float BASE_X = original_positions_orni_[i].x + std::round(SHADOW_OX);
|
||||||
const float BASE_Y = posicions_originals_orni_[i].y + std::round(SHADOW_OY);
|
const float BASE_Y = original_positions_orni_[i].y + std::round(SHADOW_OY);
|
||||||
const Vec2 POS_SHADOW{
|
const Vec2 POS_SHADOW{
|
||||||
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
|
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
|
||||||
.y = SCREEN_CENTRE_Y + (LOGO_S * (BASE_Y - SCREEN_CENTRE_Y)),
|
.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) {
|
for (std::size_t i = 0; i < letters_attack_.size(); ++i) {
|
||||||
const float BASE_X = posicions_originals_attack_[i].x + std::round(SHADOW_OX);
|
const float BASE_X = original_positions_attack_[i].x + std::round(SHADOW_OX);
|
||||||
const float BASE_Y = posicions_originals_attack_[i].y + std::round(SHADOW_OY);
|
const float BASE_Y = original_positions_attack_[i].y + std::round(SHADOW_OY);
|
||||||
const Vec2 POS_SHADOW{
|
const Vec2 POS_SHADOW{
|
||||||
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
|
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
|
||||||
.y = SCREEN_CENTRE_Y + (LOGO_S * (BASE_Y - SCREEN_CENTRE_Y)),
|
.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{
|
const Vec2 POS{
|
||||||
.x = SCREEN_CENTRE_X + (LOGO_S * (lletra.position.x - SCREEN_CENTRE_X)),
|
.x = SCREEN_CENTRE_X + (LOGO_S * (letter.position.x - SCREEN_CENTRE_X)),
|
||||||
.y = SCREEN_CENTRE_Y + (LOGO_S * (lletra.position.y - SCREEN_CENTRE_Y)),
|
.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{
|
const Vec2 POS{
|
||||||
.x = SCREEN_CENTRE_X + (LOGO_S * (lletra.position.x - SCREEN_CENTRE_X)),
|
.x = SCREEN_CENTRE_X + (LOGO_S * (letter.position.x - SCREEN_CENTRE_X)),
|
||||||
.y = SCREEN_CENTRE_Y + (LOGO_S * (lletra.position.y - SCREEN_CENTRE_Y)),
|
.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;
|
const float SPACING = Defaults::Title::Layout::TEXT_SPACING;
|
||||||
|
|
||||||
if (press_start_visible_) {
|
if (press_start_visible_) {
|
||||||
bool mostrar_text = true;
|
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>;
|
const float FASE = temps_acumulat_ * BLINK_FREQUENCY * 2.0F * std::numbers::pi_v<float>;
|
||||||
mostrar_text = (std::sin(FASE) > 0.0F);
|
mostrar_text = (std::sin(FASE) > 0.0F);
|
||||||
}
|
}
|
||||||
@@ -622,14 +622,14 @@ auto TitleScene::checkStartGameButtonPressed() -> bool {
|
|||||||
bool any_pressed = false;
|
bool any_pressed = false;
|
||||||
for (auto action : START_GAME_BUTTONS) {
|
for (auto action : START_GAME_BUTTONS) {
|
||||||
if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT)) {
|
if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||||
if (!match_config_.jugador1_actiu) {
|
if (!match_config_.player1_active) {
|
||||||
match_config_.jugador1_actiu = true;
|
match_config_.player1_active = true;
|
||||||
any_pressed = true;
|
any_pressed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) {
|
if (input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||||
if (!match_config_.jugador2_actiu) {
|
if (!match_config_.player2_active) {
|
||||||
match_config_.jugador2_actiu = true;
|
match_config_.player2_active = true;
|
||||||
any_pressed = true;
|
any_pressed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ class TitleScene final : public Scene {
|
|||||||
BLACK_SCREEN,
|
BLACK_SCREEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LetraLogo {
|
struct LogoLetter {
|
||||||
std::shared_ptr<Graphics::Shape> shape;
|
std::shared_ptr<Graphics::Shape> shape;
|
||||||
Vec2 position;
|
Vec2 position;
|
||||||
float ancho;
|
float width;
|
||||||
float altura;
|
float height;
|
||||||
float offset_centre;
|
float center_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
SDLManager& sdl_;
|
SDLManager& sdl_;
|
||||||
@@ -77,22 +77,22 @@ class TitleScene final : public Scene {
|
|||||||
void triggerFlash(Vec2 pos);
|
void triggerFlash(Vec2 pos);
|
||||||
void updateFlashes(float delta_time);
|
void updateFlashes(float delta_time);
|
||||||
void drawFlashes();
|
void drawFlashes();
|
||||||
TitleState estat_actual_{TitleState::STARFIELD_FADE_IN};
|
TitleState current_state_{TitleState::STARFIELD_FADE_IN};
|
||||||
float temps_acumulat_{0.0F};
|
float temps_acumulat_{0.0F};
|
||||||
|
|
||||||
std::vector<LetraLogo> lletres_orni_;
|
std::vector<LogoLetter> letters_orni_;
|
||||||
std::vector<LetraLogo> lletres_attack_;
|
std::vector<LogoLetter> letters_attack_;
|
||||||
float y_attack_dinamica_{0.0F};
|
float dynamic_attack_y_{0.0F};
|
||||||
|
|
||||||
std::vector<LetraLogo> lletres_jailgames_;
|
std::vector<LogoLetter> letters_jailgames_;
|
||||||
|
|
||||||
float temps_animacio_{0.0F};
|
float animation_time_{0.0F};
|
||||||
std::vector<Vec2> posicions_originals_orni_;
|
std::vector<Vec2> original_positions_orni_;
|
||||||
std::vector<Vec2> posicions_originals_attack_;
|
std::vector<Vec2> original_positions_attack_;
|
||||||
|
|
||||||
float temps_estat_main_{0.0F};
|
float state_time_main_{0.0F};
|
||||||
bool animacio_activa_{false};
|
bool animation_active_{false};
|
||||||
float factor_lerp_{0.0F};
|
float lerp_factor_{0.0F};
|
||||||
|
|
||||||
// Progresos de la intro coreografiada al state MAIN.
|
// Progresos de la intro coreografiada al state MAIN.
|
||||||
float intro_logo_progress_{0.0F};
|
float intro_logo_progress_{0.0F};
|
||||||
@@ -102,12 +102,12 @@ class TitleScene final : public Scene {
|
|||||||
bool ships_intro_launched_{false};
|
bool ships_intro_launched_{false};
|
||||||
|
|
||||||
static constexpr float BRIGHTNESS_STARFIELD = 1.2F;
|
static constexpr float BRIGHTNESS_STARFIELD = 1.2F;
|
||||||
static constexpr float DURACIO_FADE_IN = 3.0F;
|
static constexpr float DURATION_FADE_IN = 3.0F;
|
||||||
static constexpr float DURACIO_INIT = 4.0F;
|
static constexpr float DURATION_INIT = 4.0F;
|
||||||
static constexpr float DURACIO_TRANSITION = 2.5F;
|
static constexpr float DURATION_TRANSITION = 2.5F;
|
||||||
static constexpr float ESPAI_ENTRE_LLETRES = 10.0F;
|
static constexpr float LETTER_SPACING = 10.0F;
|
||||||
static constexpr float BLINK_FREQUENCY = 3.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 int MUSIC_FADE = 1500;
|
||||||
|
|
||||||
static constexpr float ORBIT_AMPLITUDE_X = 4.0F;
|
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_X = 2.0F;
|
||||||
static constexpr float SHADOW_OFFSET_Y = 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.
|
// Càmera 3D: FOV vertical en radians.
|
||||||
static constexpr float CAMERA_FOV_Y_RAD = 1.0472F; // 60°
|
static constexpr float CAMERA_FOV_Y_RAD = 1.0472F; // 60°
|
||||||
|
|||||||
@@ -16,152 +16,152 @@
|
|||||||
|
|
||||||
namespace StageSystem {
|
namespace StageSystem {
|
||||||
|
|
||||||
SpawnController::SpawnController() = default;
|
SpawnController::SpawnController() = default;
|
||||||
|
|
||||||
void SpawnController::configure(const StageConfig* config) {
|
void SpawnController::configure(const StageConfig* config) {
|
||||||
config_ = config;
|
config_ = config;
|
||||||
}
|
|
||||||
|
|
||||||
void SpawnController::start() {
|
|
||||||
if (config_ == nullptr) {
|
|
||||||
std::cerr << "[SpawnController] Error: config_ es null" << '\n';
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
void SpawnController::start() {
|
||||||
generateSpawnEvents();
|
if (config_ == nullptr) {
|
||||||
|
std::cerr << "[SpawnController] Error: config_ es null" << '\n';
|
||||||
std::cout << "[SpawnController] Stage " << static_cast<int>(config_->stage_id)
|
return;
|
||||||
<< ": generats " << spawn_queue_.size() << " spawn events" << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if ((config_ == nullptr) || spawn_queue_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment timer only when not paused
|
|
||||||
if (!pausar) {
|
|
||||||
temps_transcorregut_ += delta_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process spawn events
|
|
||||||
while (index_spawn_actual_ < spawn_queue_.size()) {
|
|
||||||
SpawnEvent& event = spawn_queue_[index_spawn_actual_];
|
|
||||||
|
|
||||||
if (event.spawnejat) {
|
|
||||||
index_spawn_actual_++;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (temps_transcorregut_ >= event.temps_spawn) {
|
reset();
|
||||||
// Find first inactive enemy
|
generateSpawnEvents();
|
||||||
for (auto& enemy : orni_array) {
|
|
||||||
if (!enemy.isActive()) {
|
std::cout << "[SpawnController] Stage " << static_cast<int>(config_->stage_id)
|
||||||
spawnEnemy(enemy, event.type, ship_position_);
|
<< ": generats " << spawn_queue_.size() << " spawn events" << '\n';
|
||||||
event.spawnejat = true;
|
}
|
||||||
index_spawn_actual_++;
|
|
||||||
|
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) {
|
||||||
|
if ((config_ == nullptr) || spawn_queue_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment timer only when not paused
|
||||||
|
if (!pausar) {
|
||||||
|
temps_transcorregut_ += delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process spawn events
|
||||||
|
while (index_spawn_actual_ < spawn_queue_.size()) {
|
||||||
|
SpawnEvent& event = spawn_queue_[index_spawn_actual_];
|
||||||
|
|
||||||
|
if (event.spawnejat) {
|
||||||
|
index_spawn_actual_++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temps_transcorregut_ >= event.temps_spawn) {
|
||||||
|
// Find first inactive enemy
|
||||||
|
for (auto& enemy : orni_array) {
|
||||||
|
if (!enemy.isActive()) {
|
||||||
|
spawnEnemy(enemy, event.type, ship_position_);
|
||||||
|
event.spawnejat = true;
|
||||||
|
index_spawn_actual_++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no slot available, try next frame
|
||||||
|
if (!event.spawnejat) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// Not yet time for this spawn
|
||||||
// If no slot available, try next frame
|
|
||||||
if (!event.spawnejat) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Not yet time for this spawn
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::allEnemiesSpawned() const -> bool {
|
auto SpawnController::allEnemiesSpawned() const -> bool {
|
||||||
return index_spawn_actual_ >= spawn_queue_.size();
|
return index_spawn_actual_ >= spawn_queue_.size();
|
||||||
}
|
|
||||||
|
|
||||||
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::allEnemiesDestroyed(const std::array<Enemy, 15>& orni_array) const -> bool {
|
||||||
uint8_t count = 0;
|
if (!allEnemiesSpawned()) {
|
||||||
for (const auto& enemy : orni_array) {
|
return false;
|
||||||
if (enemy.isActive()) {
|
}
|
||||||
count++;
|
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 {
|
||||||
|
uint8_t count = 0;
|
||||||
|
for (const auto& enemy : orni_array) {
|
||||||
|
if (enemy.isActive()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SpawnController::countSpawnedEnemies() const -> uint8_t {
|
||||||
|
return static_cast<uint8_t>(index_spawn_actual_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnController::generateSpawnEvents() {
|
||||||
|
if (config_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < config_->total_enemies; i++) {
|
||||||
|
float spawn_time = config_->config_spawn.delay_inicial +
|
||||||
|
(i * config_->config_spawn.interval_spawn);
|
||||||
|
|
||||||
|
EnemyType type = selectRandomType();
|
||||||
|
|
||||||
|
spawn_queue_.push_back({spawn_time, type, false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::countSpawnedEnemies() const -> uint8_t {
|
auto SpawnController::selectRandomType() const -> EnemyType {
|
||||||
return static_cast<uint8_t>(index_spawn_actual_);
|
if (config_ == nullptr) {
|
||||||
}
|
return EnemyType::PENTAGON;
|
||||||
|
}
|
||||||
|
|
||||||
void SpawnController::generateSpawnEvents() {
|
// Weighted random selection based on distribution
|
||||||
if (config_ == nullptr) {
|
int rand_val = std::rand() % 100;
|
||||||
return;
|
|
||||||
|
if (std::cmp_less(rand_val, config_->distribucio.pentagon)) {
|
||||||
|
return EnemyType::PENTAGON;
|
||||||
|
}
|
||||||
|
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) {
|
||||||
|
return EnemyType::SQUARE;
|
||||||
|
}
|
||||||
|
return EnemyType::PINWHEEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint8_t i = 0; i < config_->total_enemies; i++) {
|
void SpawnController::spawnEnemy(Enemy& enemy, EnemyType type, const Vec2* ship_pos) {
|
||||||
float spawn_time = config_->config_spawn.delay_inicial +
|
// Initialize enemy (with safe spawn if ship_pos provided)
|
||||||
(i * config_->config_spawn.interval_spawn);
|
enemy.init(type, ship_pos);
|
||||||
|
|
||||||
EnemyType type = selectRandomType();
|
// Apply difficulty multipliers
|
||||||
|
applyMultipliers(enemy);
|
||||||
spawn_queue_.push_back({spawn_time, type, false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto SpawnController::selectRandomType() const -> EnemyType {
|
|
||||||
if (config_ == nullptr) {
|
|
||||||
return EnemyType::PENTAGON;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weighted random selection based on distribution
|
void SpawnController::applyMultipliers(Enemy& enemy) const {
|
||||||
int rand_val = std::rand() % 100;
|
if (config_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (std::cmp_less(rand_val, config_->distribucio.pentagon)) {
|
// Apply velocity multiplier
|
||||||
return EnemyType::PENTAGON;
|
float base_vel = enemy.getBaseVelocity();
|
||||||
|
enemy.setVelocity(base_vel * config_->multiplicadors.velocity);
|
||||||
|
|
||||||
|
// Apply rotation multiplier
|
||||||
|
float base_rot = enemy.getBaseRotation();
|
||||||
|
enemy.setRotation(base_rot * config_->multiplicadors.rotation);
|
||||||
|
|
||||||
|
// Apply tracking strength (only affects SQUARE)
|
||||||
|
enemy.setTrackingStrength(config_->multiplicadors.tracking_strength);
|
||||||
}
|
}
|
||||||
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) {
|
|
||||||
return EnemyType::QUADRAT;
|
|
||||||
}
|
|
||||||
return EnemyType::MOLINILLO;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if (config_ == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply velocity multiplier
|
|
||||||
float base_vel = enemy.getBaseVelocity();
|
|
||||||
enemy.setVelocity(base_vel * config_->multiplicadors.velocity);
|
|
||||||
|
|
||||||
// Apply rotation multiplier
|
|
||||||
float base_rot = enemy.getBaseRotation();
|
|
||||||
enemy.setRotation(base_rot * config_->multiplicadors.rotation);
|
|
||||||
|
|
||||||
// Apply tracking strength (only affects QUADRAT)
|
|
||||||
enemy.setTrackingStrength(config_->multiplicadors.tracking_strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace StageSystem
|
} // namespace StageSystem
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace StageSystem {
|
|||||||
struct MultiplicadorsDificultat {
|
struct MultiplicadorsDificultat {
|
||||||
float velocity; // 0.5-2.0 típic
|
float velocity; // 0.5-2.0 típic
|
||||||
float rotation; // 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
|
// Metadades del file YAML
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ namespace Systems::Collision {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
return Defaults::Enemies::Scoring::PENTAGON_SCORE;
|
return Defaults::Enemies::Scoring::PENTAGON_SCORE;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
return Defaults::Enemies::Scoring::QUADRAT_SCORE;
|
return Defaults::Enemies::Scoring::SQUARE_SCORE;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
return Defaults::Enemies::Scoring::MOLINILLO_SCORE;
|
return Defaults::Enemies::Scoring::PINWHEEL_SCORE;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -32,10 +32,10 @@ namespace Systems::Collision {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case EnemyType::PENTAGON:
|
case EnemyType::PENTAGON:
|
||||||
return Defaults::Palette::PENTAGON;
|
return Defaults::Palette::PENTAGON;
|
||||||
case EnemyType::QUADRAT:
|
case EnemyType::SQUARE:
|
||||||
return Defaults::Palette::QUADRAT;
|
return Defaults::Palette::SQUARE;
|
||||||
case EnemyType::MOLINILLO:
|
case EnemyType::PINWHEEL:
|
||||||
return Defaults::Palette::MOLINILLO;
|
return Defaults::Palette::PINWHEEL;
|
||||||
}
|
}
|
||||||
return SDL_Color{};
|
return SDL_Color{};
|
||||||
}
|
}
|
||||||
@@ -58,16 +58,16 @@ namespace Systems::Collision {
|
|||||||
}
|
}
|
||||||
ctx.floating_score_manager.crear(POINTS, ENEMY_POS);
|
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;
|
const Vec2 INHERITED_VEL = ENEMY_VEL * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
|
||||||
ctx.debris_manager.explode(
|
ctx.debris_manager.explode(
|
||||||
SHAPE,
|
SHAPE,
|
||||||
ENEMY_POS,
|
ENEMY_POS,
|
||||||
0.0F, // angle (rotación interna del enemy)
|
0.0F, // angle (rotación interna del enemy)
|
||||||
1.0F, // escala
|
1.0F, // escala
|
||||||
VELOCITAT_EXPLOSIO,
|
SPEED_EXPLOSIO,
|
||||||
BRIGHTNESS,
|
BRIGHTNESS,
|
||||||
INHERITED_VEL,
|
INHERITED_VEL,
|
||||||
0.0F, // sense herència angular: evita que els 5 trossos curvin en bloc
|
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);
|
explodeNow(ctx, enemy, SHOOTER);
|
||||||
} else {
|
} else {
|
||||||
// Primer impacto → entra en estado herido (explosión diferida).
|
// Primer impacto → entra en estado herido (explosión diferida).
|
||||||
enemy.herir(SHOOTER);
|
enemy.hurt(SHOOTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
breakBullet(ctx.debris_manager, bullet);
|
breakBullet(ctx.debris_manager, bullet);
|
||||||
@@ -182,9 +182,9 @@ namespace Systems::Collision {
|
|||||||
}
|
}
|
||||||
// El sano queda herido, propagando el shooter original.
|
// El sano queda herido, propagando el shooter original.
|
||||||
if (A_WOUNDED) {
|
if (A_WOUNDED) {
|
||||||
b.herir(a.getLastHitBy());
|
b.hurt(a.getLastHitBy());
|
||||||
} else {
|
} else {
|
||||||
a.herir(b.getLastHitBy());
|
a.hurt(b.getLastHitBy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ namespace Systems::Collision {
|
|||||||
} else {
|
} else {
|
||||||
// Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
|
// Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
|
||||||
// l'enemic no rep dany per decisió de disseny).
|
// l'enemic no rep dany per decisió de disseny).
|
||||||
ctx.ships[i].herir();
|
ctx.ships[i].hurt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.ships[i].setTouchingEnemyPrevFrame(TOUCHING_NOW);
|
ctx.ships[i].setTouchingEnemyPrevFrame(TOUCHING_NOW);
|
||||||
@@ -262,8 +262,8 @@ namespace Systems::Collision {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const bool JUGADOR_ACTIU = (player_id == 0)
|
const bool JUGADOR_ACTIU = (player_id == 0)
|
||||||
? ctx.match_config.jugador1_actiu
|
? ctx.match_config.player1_active
|
||||||
: ctx.match_config.jugador2_actiu;
|
: ctx.match_config.player2_active;
|
||||||
if (!JUGADOR_ACTIU) {
|
if (!JUGADOR_ACTIU) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ namespace Systems::Collision {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void desactivateOutOfBoundsBullets(
|
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) {
|
Effects::DebrisManager& debris_manager) {
|
||||||
float min_x;
|
float min_x;
|
||||||
float max_x;
|
float max_x;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Systems::Collision {
|
|||||||
struct Context {
|
struct Context {
|
||||||
std::array<Ship, 2>& ships;
|
std::array<Ship, 2>& ships;
|
||||||
std::array<Enemy, Defaults::Entities::MAX_ORNIS>& enemies;
|
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<float, 2>& hit_timer_per_player;
|
||||||
std::array<int, 2>& score_per_player;
|
std::array<int, 2>& score_per_player;
|
||||||
std::array<int, 2>& lives_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()
|
// (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.
|
// perquè una bala que el mateix frame xoca i alhora surt es comptabilitzi com a impacte.
|
||||||
void desactivateOutOfBoundsBullets(
|
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);
|
Effects::DebrisManager& debris_manager);
|
||||||
|
|
||||||
} // namespace Systems::Collision
|
} // namespace Systems::Collision
|
||||||
|
|||||||
@@ -11,99 +11,99 @@
|
|||||||
#include "game/scenes/game_scene.hpp" // GameOverState (definición completa)
|
#include "game/scenes/game_scene.hpp" // GameOverState (definición completa)
|
||||||
|
|
||||||
namespace Systems::ContinueScreen {
|
namespace Systems::ContinueScreen {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Si el countdown ha bajado de 0, transiciona a GAME_OVER con su timer.
|
// Si el countdown ha bajado de 0, transiciona a GAME_OVER con su timer.
|
||||||
void checkAndApplyTimeout(Context& ctx) {
|
void checkAndApplyTimeout(Context& ctx) {
|
||||||
if (ctx.counter < 0) {
|
if (ctx.counter < 0) {
|
||||||
ctx.state = GameOverState::GAME_OVER;
|
ctx.state = GameOverState::GAME_OVER;
|
||||||
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
|
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.score_per_player[player_id] = 0;
|
||||||
ctx.lives_per_player[player_id] = Defaults::Game::STARTING_LIVES;
|
ctx.lives_per_player[player_id] = Defaults::Game::STARTING_LIVES;
|
||||||
ctx.hit_timer_per_player[player_id] = 0.0F;
|
ctx.hit_timer_per_player[player_id] = 0.0F;
|
||||||
|
|
||||||
if (player_id == 0) {
|
if (player_id == 0) {
|
||||||
ctx.match_config.jugador1_actiu = true;
|
ctx.match_config.player1_active = true;
|
||||||
} else {
|
} else {
|
||||||
ctx.match_config.jugador2_actiu = true;
|
ctx.match_config.player2_active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Vec2 SPAWN = ctx.get_spawn_point(player_id);
|
const Vec2 SPAWN = ctx.get_spawn_point(player_id);
|
||||||
ctx.ships[player_id].init(&SPAWN, /*activar_invulnerabilitat=*/true);
|
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;
|
ctx.tick_timer -= delta_time;
|
||||||
if (ctx.tick_timer > 0.0F) {
|
if (ctx.tick_timer > 0.0F) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.counter--;
|
|
||||||
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
|
|
||||||
|
|
||||||
checkAndApplyTimeout(ctx);
|
|
||||||
|
|
||||||
// Solo pita el tick si seguimos en CONTINUE (no transitamos a GAME_OVER)
|
|
||||||
if (ctx.state == GameOverState::CONTINUE) {
|
|
||||||
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void processInput(Context& ctx) {
|
|
||||||
auto* input = Input::get();
|
|
||||||
|
|
||||||
const bool P1_START = input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
|
||||||
const bool P2_START = input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
|
||||||
|
|
||||||
if (P1_START || P2_START) {
|
|
||||||
// ¿Quedan continues?
|
|
||||||
if (!Defaults::Game::INFINITE_CONTINUES &&
|
|
||||||
ctx.continues_used >= Defaults::Game::MAX_CONTINUES) {
|
|
||||||
ctx.state = GameOverState::GAME_OVER;
|
|
||||||
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Defaults::Game::INFINITE_CONTINUES) {
|
|
||||||
ctx.continues_used++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t PRIMARY = P1_START ? 0 : 1;
|
|
||||||
revivePlayer(ctx, PRIMARY);
|
|
||||||
// Si ambos pulsan START, revivimos a los dos.
|
|
||||||
if (P1_START && P2_START) {
|
|
||||||
revivePlayer(ctx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reanudar partida.
|
|
||||||
ctx.state = GameOverState::NONE;
|
|
||||||
ctx.counter = 0;
|
|
||||||
ctx.tick_timer = 0.0F;
|
|
||||||
|
|
||||||
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// THRUST/SHOOT acelera el countdown manualmente.
|
|
||||||
const bool ACCEL = input->checkActionPlayer1(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
|
|
||||||
input->checkActionPlayer2(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
|
|
||||||
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT) ||
|
|
||||||
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT);
|
|
||||||
|
|
||||||
if (ACCEL) {
|
|
||||||
ctx.counter--;
|
ctx.counter--;
|
||||||
|
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||||
|
|
||||||
checkAndApplyTimeout(ctx);
|
checkAndApplyTimeout(ctx);
|
||||||
|
|
||||||
|
// Solo pita el tick si seguimos en CONTINUE (no transitamos a GAME_OVER)
|
||||||
if (ctx.state == GameOverState::CONTINUE) {
|
if (ctx.state == GameOverState::CONTINUE) {
|
||||||
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void processInput(Context& ctx) {
|
||||||
|
auto* input = Input::get();
|
||||||
|
|
||||||
|
const bool P1_START = input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
||||||
|
const bool P2_START = input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
||||||
|
|
||||||
|
if (P1_START || P2_START) {
|
||||||
|
// ¿Quedan continues?
|
||||||
|
if (!Defaults::Game::INFINITE_CONTINUES &&
|
||||||
|
ctx.continues_used >= Defaults::Game::MAX_CONTINUES) {
|
||||||
|
ctx.state = GameOverState::GAME_OVER;
|
||||||
|
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Defaults::Game::INFINITE_CONTINUES) {
|
||||||
|
ctx.continues_used++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t PRIMARY = P1_START ? 0 : 1;
|
||||||
|
revivePlayer(ctx, PRIMARY);
|
||||||
|
// Si ambos pulsan START, revivimos a los dos.
|
||||||
|
if (P1_START && P2_START) {
|
||||||
|
revivePlayer(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reanudar partida.
|
||||||
|
ctx.state = GameOverState::NONE;
|
||||||
|
ctx.counter = 0;
|
||||||
|
ctx.tick_timer = 0.0F;
|
||||||
|
|
||||||
|
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// THRUST/SHOOT acelera el countdown manualmente.
|
||||||
|
const bool ACCEL = input->checkActionPlayer1(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
|
||||||
|
input->checkActionPlayer2(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
|
||||||
|
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT) ||
|
||||||
|
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT);
|
||||||
|
|
||||||
|
if (ACCEL) {
|
||||||
|
ctx.counter--;
|
||||||
|
checkAndApplyTimeout(ctx);
|
||||||
|
|
||||||
|
if (ctx.state == GameOverState::CONTINUE) {
|
||||||
|
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Systems::ContinueScreen
|
} // namespace Systems::ContinueScreen
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace Systems::InitHud {
|
|||||||
auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 {
|
auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 {
|
||||||
const float EASED = Easing::easeOutQuad(progress);
|
const float EASED = Easing::easeOutQuad(progress);
|
||||||
const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
|
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_INI = zone.y + zone.h + Defaults::Hud::InitAnim::SHIP_SPAWN_Y_OFFSET;
|
||||||
const float Y_ANIM = Y_INI + ((final_position.y - Y_INI) * EASED);
|
const float Y_ANIM = Y_INI + ((final_position.y - Y_INI) * EASED);
|
||||||
return Vec2{.x = final_position.x, .y = Y_ANIM};
|
return Vec2{.x = final_position.x, .y = Y_ANIM};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// Cubre la animación INIT_HUD del comienzo de cada partida/stage:
|
// 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.
|
// 1. Crecimiento de los marcos del PLAYAREA con efecto pincel en 3 fases.
|
||||||
// 2. Marcador subiendo desde abajo.
|
// 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
|
// Todas las funciones son puras (sin estado interno propio). GameScene aporta
|
||||||
// el contexto que necesitan: posiciones finales, texto del scoreboard y el
|
// el contexto que necesitan: posiciones finales, texto del scoreboard y el
|
||||||
@@ -21,29 +21,29 @@
|
|||||||
|
|
||||||
namespace Systems::InitHud {
|
namespace Systems::InitHud {
|
||||||
|
|
||||||
// Convierte un progreso global 0..1 al sub-progreso de un elemento que solo
|
// Convierte un progreso global 0..1 al sub-progreso de un elemento que solo
|
||||||
// se anima en la ventana [ratio_init, ratio_end].
|
// se anima en la ventana [ratio_init, ratio_end].
|
||||||
// < ratio_init → 0.0 (no empezó)
|
// < ratio_init → 0.0 (no empezó)
|
||||||
// > ratio_end → 1.0 (terminó)
|
// > ratio_end → 1.0 (terminó)
|
||||||
// en rango → interpolación lineal 0..1
|
// en rango → interpolación lineal 0..1
|
||||||
[[nodiscard]] auto computeRangeProgress(float global_progress,
|
[[nodiscard]] auto computeRangeProgress(float global_progress,
|
||||||
float ratio_init,
|
float ratio_init,
|
||||||
float ratio_end) -> float;
|
float ratio_end) -> float;
|
||||||
|
|
||||||
// Calcula posición Y animada de una nave durante INIT_HUD. La nave sube
|
// 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.
|
// desde 50 px bajo el PLAYAREA hasta `final_position` con easing.
|
||||||
[[nodiscard]] auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2;
|
[[nodiscard]] auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2;
|
||||||
|
|
||||||
// Dibuja los 4 lados del PLAYAREA con efecto pincel en 3 fases:
|
// Dibuja los 4 lados del PLAYAREA con efecto pincel en 3 fases:
|
||||||
// 0..33% → línea superior crece desde el centro hacia los lados.
|
// 0..33% → línea superior crece desde el centro hacia los lados.
|
||||||
// 33..66% → líneas verticales bajan por los laterales.
|
// 33..66% → líneas verticales bajan por los laterales.
|
||||||
// 66..100% → línea inferior crece desde los lados hacia el centro.
|
// 66..100% → línea inferior crece desde los lados hacia el centro.
|
||||||
void drawBordersAnimated(Rendering::Renderer* renderer, float progress);
|
void drawBordersAnimated(Rendering::Renderer* renderer, float progress);
|
||||||
|
|
||||||
// Dibuja el scoreboard centrado, subiendo desde fuera de la pantalla
|
// Dibuja el scoreboard centrado, subiendo desde fuera de la pantalla
|
||||||
// hasta su posición final con easing.
|
// hasta su posición final con easing.
|
||||||
void drawScoreboardAnimated(const Graphics::VectorText& text,
|
void drawScoreboardAnimated(const Graphics::VectorText& text,
|
||||||
const std::string& scoreboard_text,
|
const std::string& scoreboard_text,
|
||||||
float progress);
|
float progress);
|
||||||
|
|
||||||
} // namespace Systems::InitHud
|
} // namespace Systems::InitHud
|
||||||
|
|||||||
Reference in New Issue
Block a user