Fase 1e: cierre de naming sweep (#pragma once, locals, comentarios castellano)

Tres tareas de pulido para cerrar la Fase 1 por completo:

#pragma once uniforme:
- sdl_manager.hpp y game_scene.hpp pasan de #ifndef/#define guards
  a #pragma once. Los archivos externos (stb_vorbis.h, fkyaml_node.hpp)
  se mantienen intactos (codigo de terceros).

Variables locales y parametros restantes (catalan -> ingles):
- fitxer -> file, moviment -> movement, inici -> start
- comptador -> counter, escalada -> scaled
- missatges -> messages, llista -> list
- alçada -> height, amplada -> width, llargada -> length
- origen -> origin, distancia -> distance, valor -> value, desti -> target
- neteja -> clear, presenta -> present (SDLManager)
- total_enemics -> total_enemies, configurar -> configure, iniciar -> start

Comentarios catalan -> castellano:
- Cabeceras de fichero actualizadas con nombres nuevos
  (escena_joc.hpp -> game_scene.hpp, etc.)
- Palabras tecnicas: trasllacio->traslacion, col-lisio->colision,
  inicialitzacio->inicializacion, posicio->posicion, rotacio->rotacion,
  velocitat->velocidad, acceleracio->aceleracion, explosio->explosion,
  renderitzat->renderizado, calcul->calculo, transicio->transicion,
  comprovacio->comprobacion, substitucio->sustitucion,
  utilitzacio->utilizacion, opcio->opcion, configuracio->configuracion,
  funcio->funcion, distancia, animacio->animacion
- Determinantes y conectores: aquest->este, aquesta->esta,
  amb->con, sense->sin, pero->pero, mai->nunca, nomes->solo,
  tambe->tambien, sempre->siempre, ja->ya, mateix->mismo,
  vegada->vez, dintre->dentro, fora->fuera, dreta->derecha,
  esquerra->izquierda, sortir->salir, sortida->salida,
  petit->pequeno, gran->grande, nou->nuevo, vell->viejo,
  molt->mucho, els->los, les->las, totes les->todas las,
  d'->de, com->como, quan->cuando, mentre->mientras,
  despres->despues, abans->antes, durant->durante, fins->hasta,
  encara->aun, llavors->entonces, aixi->asi, perque->porque
- Sustantivos: classe->clase, metode->metodo, parametre->parametro,
  versio->version, entitat->entidad, joc->juego, nivell->nivel,
  enemic->enemigo, naus->naves, bales->balas, fitxer->archivo,
  pentagon->pentagono, pun- tuacio->puntuacion, flotant->flotante,
  titol->titulo, objectiu->objetivo, mostra->muestra, tipus->tipo

Strings literales preservados en valenciano segun decision del
usuario: el texto del HUD del juego (puntuaciones, mensajes en
pantalla, archivo de config) se mantiene en valenciano original.

70 fitxers tocats, +1117 / -1123. Compila i enllaca.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
JailDesignerandClaude Opus 4.7 committed 2026-05-19 12:12:30 +02:00
1 parent 7ee359b910
commit bf83f161b0
71 files changed
+1142 -1148

No files matched your search

+5 -5
View File
@@ -2,11 +2,11 @@
#include "core/defaults.hpp"
// Aliases per a backward compatibility amb codi existent
// Aliases para backward compatibility con codi existent
// Permet usar Constants::MARGIN_LEFT en lloc de Defaults::Game::MARGIN_LEFT
namespace Constants {
// Marges de l'àrea de joc (derivats de Defaults::Zones::GAME)
// Marges de l'àrea de juego (derivats de Defaults::Zones::GAME)
constexpr int MARGIN_LEFT = static_cast<int>(Defaults::Zones::PLAYAREA.x);
constexpr int MARGIN_RIGHT =
static_cast<int>(Defaults::Zones::PLAYAREA.x + Defaults::Zones::PLAYAREA.w);
@@ -14,7 +14,7 @@ constexpr int MARGIN_TOP = static_cast<int>(Defaults::Zones::PLAYAREA.y);
constexpr int MARGIN_BOTTOM =
static_cast<int>(Defaults::Zones::PLAYAREA.y + Defaults::Zones::PLAYAREA.h);
// Límits d'objectes
// Límits de objectes
constexpr int MAX_ORNIS = Defaults::Entities::MAX_ORNIS;
constexpr int MAX_BALES = Defaults::Entities::MAX_BALES;
@@ -39,7 +39,7 @@ inline void obtenir_limits_zona(float& min_x, float& max_x, float& min_y, float&
max_y = zona.y + zona.h;
}
// Obtenir límits segurs (compensant radi de l'entitat)
// Obtenir límits segurs (compensant radi de l'entidad)
inline void obtenir_limits_zona_segurs(float radi, float& min_x, float& max_x, float& min_y, float& max_y) {
const auto& zona = Defaults::Zones::PLAYAREA;
constexpr float MARGE_SEGURETAT = 10.0F; // Safety margin
@@ -50,7 +50,7 @@ inline void obtenir_limits_zona_segurs(float radi, float& min_x, float& max_x, f
max_y = zona.y + zona.h - radi - MARGE_SEGURETAT;
}
// Obtenir centre de l'àrea de joc
// Obtenir centro de l'àrea de juego
inline void obtenir_centre_zona(float& centre_x, float& centre_y) {
const auto& zona = Defaults::Zones::PLAYAREA;
centre_x = zona.x + (zona.w / 2.0F);
+12 -12
View File
@@ -1,33 +1,33 @@
// debris.hpp - Fragment de línia volant (explosió de formes)
// © 2025 Port a C++20 amb SDL3
// debris.hpp - Fragment de línia volant (explosión de formes)
// © 2025 Port a C++20 con SDL3
#pragma once
#include "core/types.hpp"
namespace Effects {
// Debris: un segment de línia que vola perpendicular a sí mateix
// Representa un fragment d'una shape destruïda (ship, enemy, bullet)
// Debris: un segment de línia que vola perpendicular a sí mismo
// Representa un fragment de una shape destruïda (ship, enemy, bullet)
struct Debris {
// Geometria del segment (2 points en coordenades mundials)
Vec2 p1; // Vec2 inicial del segment
Vec2 p2; // Vec2 final del segment
// Física
Vec2 velocity; // Velocitat en px/s (components x, y)
float acceleration; // Acceleració negativa (fricció) en px/s²
Vec2 velocity; // Velocidad en px/s (components x, y)
float acceleration; // Aceleración negativa (fricció) en px/s²
// Rotació
float angle_rotacio; // Angle de rotació acumulat (radians)
float velocitat_rot; // Velocitat de rotació de TRAYECTORIA (rad/s)
float velocitat_rot_visual; // Velocitat de rotació VISUAL del segment (rad/s)
// Rotación
float angle_rotacio; // Angle de rotación acumulat (radians)
float velocitat_rot; // Velocidad de rotación de TRAYECTORIA (rad/s)
float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s)
// Estat de vida
float temps_vida; // Temps transcorregut (segons)
float temps_max; // Temps de vida màxim (segons)
bool active; // Està active?
bool active; // Está active?
// Shrinking (reducció de distància entre points)
// Shrinking (reducció de distancia entre points)
float factor_shrink; // Factor de reducció per segon (0.0-1.0)
// Rendering
+52 -52
View File
@@ -1,5 +1,5 @@
// debris_manager.cpp - Implementació del gestor de fragments
// © 2025 Port a C++20 amb SDL3
// © 2025 Port a C++20 con SDL3
#include "debris_manager.hpp"
@@ -14,10 +14,10 @@
namespace Effects {
// Helper: transformar point amb rotació, scale i trasllació
// Helper: transformar point con rotación, scale i traslación
// (Copiat de shape_renderer.cpp:12-34)
static Vec2 transform_point(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) {
// 1. Centrar el point respecte al centre de la shape
// 1. Centrar el point respecte al centro de la shape
float centered_x = point.x - shape_centre.x;
float centered_y = point.y - shape_centre.y;
@@ -25,27 +25,27 @@ static Vec2 transform_point(const Vec2& point, const Vec2& shape_centre, const V
float scaled_x = centered_x * scale;
float scaled_y = centered_y * scale;
// 3. Aplicar rotació
// 3. Aplicar rotación
float cos_a = std::cos(angle);
float sin_a = std::sin(angle);
float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a);
float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a);
// 4. Aplicar trasllació a posició mundial
// 4. Aplicar traslación a posición mundial
return {.x = rotated_x + position.x, .y = rotated_y + position.y};
}
DebrisManager::DebrisManager(SDL_Renderer* renderer)
: renderer_(renderer) {
// Inicialitzar tots els debris com inactius
// Inicialitzar todos los debris como inactius
for (auto& debris : debris_pool_) {
debris.active = false;
}
}
void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
const Vec2& centre,
const Vec2& centro,
float angle,
float scale,
float velocitat_base,
@@ -61,10 +61,10 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
// Reproducir sonido de explosión
Audio::get()->playSound(sound, Audio::Group::GAME);
// Obtenir centre de la shape per a transformacions
// Obtenir centro de la shape para transformacions
const Vec2& shape_centre = shape->getCenter();
// Iterar sobre totes les primitives de la shape
// Iterar sobre todas las primitives de la shape
for (const auto& primitive : shape->get_primitives()) {
// Processar cada segment de línia
std::vector<std::pair<Vec2, Vec2>> segments;
@@ -81,13 +81,13 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
}
}
// Crear debris per a cada segment
// Crear debris para cada segment
for (const auto& [local_p1, local_p2] : segments) {
// 1. Transformar points locals → coordenades mundials
Vec2 world_p1 =
transform_point(local_p1, shape_centre, centre, angle, scale);
transform_point(local_p1, shape_centre, centro, angle, scale);
Vec2 world_p2 =
transform_point(local_p2, shape_centre, centre, angle, scale);
transform_point(local_p2, shape_centre, centro, angle, scale);
// 2. Trobar slot lliure
Debris* debris = findFreeSlot();
@@ -100,10 +100,10 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
debris->p1 = world_p1;
debris->p2 = world_p2;
// 4. Calcular direcció d'explosió (radial, des del centre cap a fora)
Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centre);
// 4. Calcular direcció de explosión (radial, des del centro hacia fuera)
Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro);
// 5. Velocitat inicial (base ± variació aleatòria + velocity heretada)
// 5. Velocidad inicial (base ± variació aleatòria + velocity heretada)
float speed =
velocitat_base +
(((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
@@ -114,11 +114,11 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
debris->acceleration = Defaults::Physics::Debris::ACCELERACIO;
// 6. Herència de velocity angular amb cap + conversió d'excés
// 6. Herència de velocity angular con sin + conversió de excés
// 6a. Rotació de TRAYECTORIA amb cap + conversió tangencial
// 6a. Rotación de TRAYECTORIA con sin + conversió tangencial
if (std::abs(velocitat_angular) > 0.01F) {
// FASE 1: Aplicar herència i variació (igual que abans)
// FASE 1: Aplicar herència i variació (igual que antes)
float factor_herencia =
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN +
((std::rand() / static_cast<float>(RAND_MAX)) *
@@ -131,7 +131,7 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
((std::rand() / static_cast<float>(RAND_MAX)) * 0.2F) - 0.1F;
velocitat_ang_heretada *= (1.0F + variacio);
// FASE 2: Aplicar cap i calcular excés
// FASE 2: Aplicar sin i calcular excés
constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX;
float abs_ang = std::abs(velocitat_ang_heretada);
float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F;
@@ -140,10 +140,10 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
// Excés: convertir a velocity tangencial
float excess = abs_ang - CAP;
// Radi de la shape (enemics = 20 px)
// Radi de la shape (enemigos = 20 px)
float radius = 20.0F;
// Velocitat tangencial = ω_excés × radi
// Velocidad tangencial = ω_excés × radi
float v_tangential = excess * radius;
// Direcció tangencial: perpendicular a la radial (90° CCW)
@@ -151,38 +151,38 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
float tangent_x = -direccio.y;
float tangent_y = direccio.x;
// Afegir velocity tangencial (suma vectorial)
// Añadir velocity tangencial (suma vectorial)
debris->velocity.x += tangent_x * v_tangential;
debris->velocity.y += tangent_y * v_tangential;
// Aplicar cap a velocity angular (preservar signe)
// Aplicar hacia velocity angular (preservar signe)
debris->velocitat_rot = sign_ang * CAP;
} else {
// Per sota del cap: comportament normal
// Per sota del sin: comportament normal
debris->velocitat_rot = velocitat_ang_heretada;
}
} else {
debris->velocitat_rot = 0.0F; // Nave: sin curvas
}
// 6b. Rotació VISUAL (proporcional según factor_herencia_visual)
// 6b. Rotación VISUAL (proporcional según factor_herencia_visual)
if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) {
// Heredar rotación visual con factor proporcional
debris->velocitat_rot_visual = debris->velocitat_rot * factor_herencia_visual;
// Variació aleatòria petita (±5%) per naturalitat
// Variació aleatòria pequeña (±5%) per naturalitat
float variacio_visual =
((std::rand() / static_cast<float>(RAND_MAX)) * 0.1F) - 0.05F;
debris->velocitat_rot_visual *= (1.0F + variacio_visual);
} else {
// Rotació 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 =
Defaults::Physics::Debris::ROTACIO_MIN +
((std::rand() / static_cast<float>(RAND_MAX)) *
(Defaults::Physics::Debris::ROTACIO_MAX -
Defaults::Physics::Debris::ROTACIO_MIN));
// 50% probabilitat de rotació en sentit contrari
// 50% probabilitat de rotación en sentit contrari
if (std::rand() % 2 == 0) {
debris->velocitat_rot_visual = -debris->velocitat_rot_visual;
}
@@ -220,7 +220,7 @@ void DebrisManager::update(float delta_time) {
}
// 2. Actualitzar velocity (desacceleració)
// Aplicar fricció en la direcció del moviment
// Aplicar fricció en la direcció del movement
float speed = std::sqrt((debris.velocity.x * debris.velocity.x) +
(debris.velocity.y * debris.velocity.y));
@@ -229,24 +229,24 @@ void DebrisManager::update(float delta_time) {
float dir_x = debris.velocity.x / speed;
float dir_y = debris.velocity.y / speed;
// Aplicar acceleració negativa (fricció)
// Aplicar aceleración negativa (fricció)
float nova_speed = speed + (debris.acceleration * delta_time);
nova_speed = std::max(nova_speed, 0.0F);
debris.velocity.x = dir_x * nova_speed;
debris.velocity.y = dir_y * nova_speed;
} else {
// Velocitat molt baixa, aturar
// Velocidad mucho baixa, aturar
debris.velocity.x = 0.0F;
debris.velocity.y = 0.0F;
}
// 2b. Rotar vector de velocity (trayectoria curva)
if (std::abs(debris.velocitat_rot) > 0.01F) {
// Calcular angle de rotació aquest frame
// Calcular angle de rotación este frame
float dangle = debris.velocitat_rot * delta_time;
// Rotar vector de velocity usant matriu de rotació 2D
// Rotar vector de velocity usant matriu de rotación 2D
float vel_x_old = debris.velocity.x;
float vel_y_old = debris.velocity.y;
@@ -270,35 +270,35 @@ void DebrisManager::update(float delta_time) {
}
}
// 3. Calcular centre del segment
Vec2 centre = {.x = (debris.p1.x + debris.p2.x) / 2.0F,
// 3. Calcular centro del segment
Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F,
.y = (debris.p1.y + debris.p2.y) / 2.0F};
// 4. Actualitzar posició del centre
centre.x += debris.velocity.x * delta_time;
centre.y += debris.velocity.y * delta_time;
// 4. Actualitzar posición del centro
centro.x += debris.velocity.x * delta_time;
centro.y += debris.velocity.y * delta_time;
// 5. Actualitzar rotació VISUAL
// 5. Actualitzar rotación VISUAL
debris.angle_rotacio += debris.velocitat_rot_visual * delta_time;
// 6. Aplicar shrinking (reducció de distància entre points)
// 6. Aplicar shrinking (reducció de distancia entre points)
float shrink_factor =
1.0F - (debris.factor_shrink * debris.temps_vida / debris.temps_max);
shrink_factor = std::max(0.0F, shrink_factor); // No negatiu
// Calcular distància original entre points
// Calcular distancia original entre points
float dx = debris.p2.x - debris.p1.x;
float dy = debris.p2.y - debris.p1.y;
// 7. Reconstruir segment amb nova mida i rotació
// 7. Reconstruir segment con nueva mida i rotación
float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F;
float original_angle = std::atan2(dy, dx);
float new_angle = original_angle + debris.angle_rotacio;
debris.p1.x = centre.x - (half_length * std::cos(new_angle));
debris.p1.y = centre.y - (half_length * std::sin(new_angle));
debris.p2.x = centre.x + (half_length * std::cos(new_angle));
debris.p2.y = centre.y + (half_length * std::sin(new_angle));
debris.p1.x = centro.x - (half_length * std::cos(new_angle));
debris.p1.y = centro.y - (half_length * std::sin(new_angle));
debris.p2.x = centro.x + (half_length * std::cos(new_angle));
debris.p2.y = centro.y + (half_length * std::sin(new_angle));
}
}
@@ -308,7 +308,7 @@ void DebrisManager::draw() const {
continue;
}
// Dibuixar segment de línia amb brightness heretat
// Dibuixar segment de línia con brightness heretat
Rendering::linea(renderer_,
static_cast<int>(debris.p1.x),
static_cast<int>(debris.p1.y),
@@ -330,19 +330,19 @@ Debris* DebrisManager::findFreeSlot() {
Vec2 DebrisManager::computeExplosionDirection(const Vec2& p1,
const Vec2& p2,
const Vec2& centre_objecte) const {
// 1. Calcular centre del segment
// 1. Calcular centro del segment
float centro_seg_x = (p1.x + p2.x) / 2.0F;
float centro_seg_y = (p1.y + p2.y) / 2.0F;
// 2. Calcular vector des del centre de l'objecte cap al centre del segment
// Això garanteix que la direcció sempre apunte cap a fora (direcció radial)
// 2. Calcular vector des del centro de l'objecte hacia el centro del segment
// Això garanteix que la direcció siempre apunte hacia fuera (direcció radial)
float dx = centro_seg_x - centre_objecte.x;
float dy = centro_seg_y - centre_objecte.y;
// 3. Normalitzar (obtenir vector unitari)
float length = std::sqrt((dx * dx) + (dy * dy));
if (length < 0.001F) {
// Segment al centre (cas extrem molt improbable), retornar direcció aleatòria
// Segment al centro (cas extrem mucho improbable), retornar direcció aleatòria
float angle_rand =
(std::rand() / static_cast<float>(RAND_MAX)) * 2.0F * Defaults::Math::PI;
return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)};
@@ -351,7 +351,7 @@ Vec2 DebrisManager::computeExplosionDirection(const Vec2& p1,
dx /= length;
dy /= length;
// 4. Afegir variació aleatòria petita (±15°) per varietat visual
// 4. Añadir variació aleatòria pequeña (±15°) per varietat visual
float angle_variacio =
((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0F;
+14 -14
View File
@@ -1,5 +1,5 @@
// debris_manager.hpp - Gestor de fragments d'explosions
// © 2025 Port a C++20 amb SDL3
// debris_manager.hpp - Gestor de fragments de explosions
// © 2025 Port a C++20 con SDL3
#pragma once
#include <SDL3/SDL.h>
@@ -14,24 +14,24 @@
namespace Effects {
// Gestor de fragments d'explosions
// Manté un pool d'objectes Debris i gestiona el seu cicle de vida
// Gestor de fragments de explosions
// Manté un pool de objectes Debris i gestiona el seu cicle de vida
class DebrisManager {
public:
explicit DebrisManager(SDL_Renderer* renderer);
// Crear explosió a partir d'una shape
// Crear explosión a partir de una shape
// - shape: shape vectorial a explode
// - centre: posició del centre de l'objecte
// - centro: posición del centro de l'objecte
// - angle: orientació de l'objecte (radians)
// - scale: scale de l'objecte (1.0 = normal)
// - velocitat_base: velocity inicial dels fragments (px/s)
// - brightness: factor de brightness heretat (0.0-1.0, per defecte 1.0)
// - velocitat_objecte: velocity de l'objecte que explota (px/s, per defecte 0)
// - velocitat_angular: velocity angular heretada (rad/s, per defecte 0)
// - factor_herencia_visual: factor de herència rotació visual (0.0-1.0, per defecte 0.0)
// - factor_herencia_visual: factor de herència rotación visual (0.0-1.0, per defecte 0.0)
void explode(const std::shared_ptr<Graphics::Shape>& shape,
const Vec2& centre,
const Vec2& centro,
float angle,
float scale,
float velocitat_base,
@@ -41,13 +41,13 @@ class DebrisManager {
float factor_herencia_visual = 0.0F,
const std::string& sound = Defaults::Sound::EXPLOSION);
// Actualitzar tots els fragments active
// Actualitzar todos los fragments active
void update(float delta_time);
// Dibuixar tots els fragments active
// Dibuixar todos los fragments active
void draw() const;
// Reiniciar tots els fragments (neteja)
// Reiniciar todos los fragments (clear)
void reset();
// Obtenir número de fragments active
@@ -57,8 +57,8 @@ class DebrisManager {
SDL_Renderer* renderer_;
// Pool de fragments (màxim concurrent)
// Un pentàgon té 5 línies, 15 enemics = 75 línies
// + ship (3 línies) + bales (5 línies * 3) = 93 línies màxim
// Un pentágono té 5 línies, 15 enemigos = 75 línies
// + ship (3 línies) + balas (5 línies * 3) = 93 línies màxim
// Arrodonit a 100 per seguretat
static constexpr int MAX_DEBRIS = 150;
std::array<Debris, MAX_DEBRIS> debris_pool_;
@@ -66,7 +66,7 @@ class DebrisManager {
// Trobar primer slot inactiu
Debris* findFreeSlot();
// Calcular direcció d'explosió (radial, des del centre cap al segment)
// Calcular direcció de explosión (radial, des del centro hacia el segment)
[[nodiscard]] Vec2 computeExplosionDirection(const Vec2& p1, const Vec2& p2, const Vec2& centre_objecte) const;
};
+8 -8
View File
@@ -1,5 +1,5 @@
// puntuacio_flotant.hpp - Número de puntuació que apareix i desapareix
// © 2025 Port a C++20 amb SDL3
// floating_score.hpp - Número de puntuación que apareix y desapareix
// © 2025 Port a C++20 con SDL3
#pragma once
@@ -9,19 +9,19 @@
namespace Effects {
// FloatingScore: text animat que mostra points guanyats
// S'activa quan es destrueix un enemy i s'esvaeix després d'un time
// FloatingScore: text animat que muestra points guanyats
// S'activa cuando es destrueix un enemy i s'esvaeix después de un time
struct FloatingScore {
// Text a mostrar (e.g., "100", "150", "200")
std::string text;
// Posició actual (coordenades mundials)
// Posición actual (coordenades mundials)
Vec2 position;
// Animació de moviment
Vec2 velocity; // px/s (normalment cap amunt: {0.0f, -30.0f})
// Animación de movement
Vec2 velocity; // px/s (normalment sin amunt: {0.0f, -30.0f})
// Animació de fade
// Animación de fade
float temps_vida; // Temps transcorregut (segons)
float temps_max; // Temps de vida màxim (segons)
float brightness; // Brillantor calculada (0.0-1.0)
@@ -1,5 +1,5 @@
// gestor_puntuacio_flotant.cpp - Implementació del gestor de números flotants
// © 2025 Port a C++20 amb SDL3
// floating_score_manager.cpp - Implementació del gestor de números flotantes
// © 2025 Port a C++20 con SDL3
#include "floating_score_manager.hpp"
@@ -9,7 +9,7 @@ namespace Effects {
FloatingScoreManager::FloatingScoreManager(SDL_Renderer* renderer)
: text_(renderer) {
// Inicialitzar tots els slots com inactius
// Inicialitzar todos los slots como inactius
for (auto& pf : pool_) {
pf.active = false;
}
@@ -22,7 +22,7 @@ void FloatingScoreManager::crear(int points, const Vec2& position) {
return; // Pool ple (improbable)
}
// 2. Inicialitzar puntuació flotant
// 2. Inicialitzar puntuación flotante
pf->text = std::to_string(points);
pf->position = position;
pf->velocity = {.x = Defaults::FloatingScore::VELOCITY_X,
@@ -39,7 +39,7 @@ void FloatingScoreManager::update(float delta_time) {
continue;
}
// 1. Actualitzar posició (deriva cap amunt)
// 1. Actualitzar posición (deriva sin amunt)
pf.position.x += pf.velocity.x * delta_time;
pf.position.y += pf.velocity.y * delta_time;
@@ -50,7 +50,7 @@ void FloatingScoreManager::update(float delta_time) {
float progress = pf.temps_vida / pf.temps_max; // 0.0 → 1.0
pf.brightness = 1.0F - progress; // 1.0 → 0.0
// 4. Desactivar quan acaba el time
// 4. Desactivar cuando acaba el time
if (pf.temps_vida >= pf.temps_max) {
pf.active = false;
}
@@ -63,7 +63,7 @@ void FloatingScoreManager::draw() {
continue;
}
// Renderitzar centrat amb brightness (fade)
// Renderizar centrat con brightness (fade)
constexpr float scale = Defaults::FloatingScore::SCALE;
constexpr float spacing = Defaults::FloatingScore::SPACING;
+11 -11
View File
@@ -1,5 +1,5 @@
// gestor_puntuacio_flotant.hpp - Gestor de números de puntuació flotants
// © 2025 Port a C++20 amb SDL3
// floating_score_manager.hpp - Gestor de números de puntuación flotantes
// © 2025 Port a C++20 con SDL3
#pragma once
@@ -14,24 +14,24 @@
namespace Effects {
// Gestor de números de puntuació flotants
// Gestor de números de puntuación flotantes
// Manté un pool de FloatingScore i gestiona el seu cicle de vida
class FloatingScoreManager {
public:
explicit FloatingScoreManager(SDL_Renderer* renderer);
// Crear número flotant
// - points: valor numèric (100, 150, 200)
// - position: on apareix (normalment centre d'enemy destruït)
// Crear número flotante
// - points: value numèric (100, 150, 200)
// - position: on apareix (normalment centro de enemy destruït)
void crear(int points, const Vec2& position);
// Actualitzar tots els números active
// Actualitzar todos los números active
void update(float delta_time);
// Dibuixar tots els números active
// Dibuixar todos los números active
void draw();
// Reiniciar tots (neteja)
// Reiniciar tots (clear)
void reset();
// Obtenir número active (debug)
@@ -40,8 +40,8 @@ class FloatingScoreManager {
private:
Graphics::VectorText text_; // Sistema de text vectorial
// Pool de números flotants (màxim concurrent)
// Màxim 15 enemics simultanis = màxim 15 números
// Pool de números flotantes (màxim concurrent)
// Màxim 15 enemigos simultanis = màxim 15 números
static constexpr int MAX_PUNTUACIONS =
Defaults::FloatingScore::MAX_CONCURRENT;
std::array<FloatingScore, MAX_PUNTUACIONS> pool_;
+12 -12
View File
@@ -1,6 +1,6 @@
// bullet.cpp - Implementació de projectils de la ship
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#include "game/entities/bullet.hpp"
@@ -23,10 +23,10 @@ Bullet::Bullet(SDL_Renderer* renderer)
esta_(false),
owner_id_(0),
grace_timer_(0.0F) {
// [NUEVO] Brightness específic per bales
// [NUEVO] Brightness específic per balas
brightness_ = Defaults::Brightness::BALA;
// [NUEVO] Carregar shape compartida des de fitxer
// [NUEVO] Carregar shape compartida desde file
shape_ = Graphics::ShapeLoader::load("bullet.shp");
if (!shape_ || !shape_->isValid()) {
@@ -50,7 +50,7 @@ void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
// Activar bullet
esta_ = true;
// Posició inicial = centre de la ship
// Posición inicial = centro de la ship
center_.x = position.x;
center_.y = position.y;
@@ -60,7 +60,7 @@ void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
// Almacenar propietario (0=P1, 1=P2)
owner_id_ = owner_id;
// Velocitat alta (el joc Pascal original usava 7 px/frame)
// Velocidad alta (el juego Pascal original usava 7 px/frame)
// 7 px/frame × 20 FPS = 140 px/s
velocity_ = 140.0F;
@@ -86,7 +86,7 @@ void Bullet::update(float delta_time) {
void Bullet::draw() const {
if (esta_ && shape_) {
// [NUEVO] Usar render_shape en lloc de rota_pol
// Les bales roten segons l'angle de trajectòria
// Les balas roten segons l'angle de trajectòria
Rendering::render_shape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_);
}
}
@@ -96,20 +96,20 @@ void Bullet::mou(float delta_time) {
// Basat en el codi Pascal original: procedure mou_bales
// Copiat EXACTAMENT de joc_asteroides.cpp línies 396-419
// Calcular nova posició (moviment polar time-based)
// velocity ja està en px/s (140 px/s), només cal multiplicar per delta_time
// Calcular nueva posición (movement polar time-based)
// velocity ya está en px/s (140 px/s), solo necesario multiplicar per delta_time
float velocitat_efectiva = velocity_ * delta_time;
// Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt)
float dy = velocitat_efectiva * std::sin(angle_ - (Constants::PI / 2.0F));
float dx = velocitat_efectiva * std::cos(angle_ - (Constants::PI / 2.0F));
// Acumulació directa amb precisió subpíxel
// Acumulació directa con precisió subpíxel
center_.y += dy;
center_.x += dx;
// Desactivar si surt de la zona de joc (no rebota com els ORNIs)
// CORRECCIÓ: Usar límits segurs amb radi de la bullet
// Desactivar si surt de la zona de juego (no rebota como los ORNIs)
// CORRECCIÓ: Usar límits segurs con radi de la bullet
float min_x;
float max_x;
float min_y;
+5 -5
View File
@@ -1,6 +1,6 @@
// bullet.hpp - Classe per a projectils de la ship
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// bullet.hpp - Clase para projectils de la ship
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#pragma once
#include <SDL3/SDL.h>
@@ -25,7 +25,7 @@ class Bullet : public Entities::Entity {
// Override: Interfície d'Entity
[[nodiscard]] bool isActive() const override { return esta_; }
// Override: Interfície de col·lisió
// Override: Interfície de colisión
[[nodiscard]] float getCollisionRadius() const override {
return Defaults::Entities::BULLET_RADIUS;
}
@@ -33,7 +33,7 @@ class Bullet : public Entities::Entity {
return esta_ && grace_timer_ <= 0.0F;
}
// Getters (API pública sense canvis)
// Getters (API pública sin canvis)
[[nodiscard]] bool esta_activa() const { return esta_; }
[[nodiscard]] uint8_t get_owner_id() const { return owner_id_; }
[[nodiscard]] float get_grace_timer() const { return grace_timer_; }
+24 -24
View File
@@ -1,6 +1,6 @@
// enemy.cpp - Implementació d'enemics (ORNIs)
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// enemy.cpp - Implementació de enemigos (ORNIs)
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#include "game/entities/enemy.hpp"
@@ -27,7 +27,7 @@ Enemy::Enemy(SDL_Renderer* renderer)
ship_position_(nullptr),
tracking_strength_(0.5F), // Default tracking strength
timer_invulnerabilitat_(0.0F) { // Start vulnerable
// [NUEVO] Brightness específic per enemics
// [NUEVO] Brightness específic per enemigos
brightness_ = Defaults::Brightness::ENEMIC;
// [NUEVO] Forma es carrega a init() segons el type
@@ -52,10 +52,10 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
break;
case EnemyType::QUADRAT:
shape_file = Defaults::Enemies::Quadrat::SHAPE_FILE;
velocity_ = Defaults::Enemies::Quadrat::VELOCITAT;
drotacio_min = Defaults::Enemies::Quadrat::DROTACIO_MIN;
drotacio_max = Defaults::Enemies::Quadrat::DROTACIO_MAX;
shape_file = Defaults::Enemies::Cuadrado::SHAPE_FILE;
velocity_ = Defaults::Enemies::Cuadrado::VELOCITAT;
drotacio_min = Defaults::Enemies::Cuadrado::DROTACIO_MIN;
drotacio_max = Defaults::Enemies::Cuadrado::DROTACIO_MAX;
tracking_timer_ = 0.0F;
break;
@@ -83,7 +83,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
std::cerr << "[Enemy] Error: no s'ha pogut load " << shape_file << '\n';
}
// [MODIFIED] Posició aleatòria amb comprovació de seguretat
// [MODIFIED] Posición aleatòria con comprobación de seguretat
float min_x;
float max_x;
float min_y;
@@ -117,7 +117,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
center_.x = static_cast<float>((std::rand() % range_x) + static_cast<int>(min_x));
center_.y = static_cast<float>((std::rand() % range_y) + static_cast<int>(min_y));
std::cout << "[Enemy] Advertència: spawn sense zona segura després de "
std::cout << "[Enemy] Advertència: spawn sin zona segura después de "
<< Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS << " intents" << '\n';
}
} else {
@@ -128,15 +128,15 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
center_.y = static_cast<float>((std::rand() % range_y) + static_cast<int>(min_y));
}
// Angle aleatori de moviment
// Angle aleatori de movement
angle_ = (std::rand() % 360) * Constants::PI / 180.0F;
// Rotació visual aleatòria (rad/s) dins del rang del type
// Rotación visual aleatòria (rad/s) dins del rang del type
float drotacio_range = drotacio_max - drotacio_min;
drotacio_ = drotacio_min + ((static_cast<float>(std::rand()) / RAND_MAX) * drotacio_range);
rotacio_ = 0.0F;
// Inicialitzar state d'animació
// Inicialitzar state de animación
animacio_ = EnemyAnimation(); // Reset to defaults
animacio_.drotacio_base = drotacio_;
animacio_.drotacio_objetivo = drotacio_;
@@ -171,10 +171,10 @@ void Enemy::update(float delta_time) {
// Moviment autònom
mou(delta_time);
// Actualitzar animacions (palpitació, rotació accelerada)
// Actualitzar animaciones (palpitació, rotación accelerada)
actualitzar_animacio(delta_time);
// Rotació visual (time-based: drotacio_ està en rad/s)
// Rotación visual (time-based: drotacio_ está en rad/s)
rotacio_ += drotacio_ * delta_time;
}
}
@@ -210,7 +210,7 @@ void Enemy::mou(float delta_time) {
void Enemy::comportament_pentagon(float delta_time) {
// Pentagon: zigzag esquivador (frequent direction changes)
// Similar a comportament original però amb probabilitat més alta
// Similar a comportament original pero con probabilitat més alta
float velocitat_efectiva = velocity_ * delta_time;
@@ -232,11 +232,11 @@ void Enemy::comportament_pentagon(float delta_time) {
min_y,
max_y);
// Zigzag: canvi d'angle més freqüent en tocar límits
// Zigzag: canvi de angle més freqüent en tocar límits
if (new_y >= min_y && new_y <= max_y) {
center_.y = new_y;
} else {
// Probabilitat més alta de canvi d'angle
// Probabilitat més alta de canvi de angle
if (static_cast<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast<float>(std::rand()) / RAND_MAX) *
Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
@@ -256,13 +256,13 @@ void Enemy::comportament_pentagon(float delta_time) {
}
void Enemy::comportament_quadrat(float delta_time) {
// Quadrat: perseguidor (tracks player position)
// Cuadrado: perseguidor (tracks player position)
// Update tracking timer
tracking_timer_ += delta_time;
// Periodically update angle toward ship
if (tracking_timer_ >= Defaults::Enemies::Quadrat::TRACKING_INTERVAL) {
if (tracking_timer_ >= Defaults::Enemies::Cuadrado::TRACKING_INTERVAL) {
tracking_timer_ = 0.0F;
if (ship_position_ != nullptr) {
@@ -488,7 +488,7 @@ float Enemy::calcular_escala_actual() const {
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
scale = START + ((END - START) * smooth_t);
} else if (animacio_.palpitacio_activa) {
// [EXISTING] Palpitació només quan no invulnerable
// [EXISTING] Palpitació solo cuando no invulnerable
scale += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase);
}
@@ -502,7 +502,7 @@ float Enemy::get_base_velocity() const {
case EnemyType::PENTAGON:
return Defaults::Enemies::Pentagon::VELOCITAT;
case EnemyType::QUADRAT:
return Defaults::Enemies::Quadrat::VELOCITAT;
return Defaults::Enemies::Cuadrado::VELOCITAT;
case EnemyType::MOLINILLO:
return Defaults::Enemies::Molinillo::VELOCITAT;
default:
@@ -544,8 +544,8 @@ bool Enemy::intent_spawn_safe(const Vec2& ship_pos, float& out_x, float& out_y)
// Check Euclidean distance to ship
float dx = out_x - ship_pos.x;
float dy = out_y - ship_pos.y;
float distancia = std::sqrt((dx * dx) + (dy * dy));
float distance = std::sqrt((dx * dx) + (dy * dy));
// Return true if position is safe (>= 36px from ship)
return distancia >= Defaults::Enemies::Spawn::SAFETY_DISTANCE;
return distance >= Defaults::Enemies::Spawn::SAFETY_DISTANCE;
}
+13 -13
View File
@@ -1,6 +1,6 @@
// enemy.hpp - Classe per a enemics (ORNIs pentàgons)
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// enemy.hpp - Clase para enemigos (ORNIs pentágonos)
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#pragma once
#include <SDL3/SDL.h>
@@ -13,14 +13,14 @@
#include "core/types.hpp"
#include "game/constants.hpp"
// Tipus d'enemy
// Tipo de enemy
enum class EnemyType : uint8_t {
PENTAGON = 0, // Pentàgon esquivador (zigzag)
QUADRAT = 1, // Quadrat perseguidor (tracks ship)
PENTAGON = 0, // Pentágono esquivador (zigzag)
QUADRAT = 1, // Cuadrado perseguidor (tracks ship)
MOLINILLO = 2 // Molinillo agressiu (fast, spinning)
};
// Estat d'animació (palpitació i rotació accelerada)
// Estat de animación (palpitació i rotación accelerada)
struct EnemyAnimation {
// Palpitation (breathing effect)
bool palpitacio_activa = false;
@@ -50,7 +50,7 @@ class Enemy : public Entities::Entity {
// Override: Interfície d'Entity
[[nodiscard]] bool isActive() const override { return esta_; }
// Override: Interfície de col·lisió
// Override: Interfície de colisión
[[nodiscard]] float getCollisionRadius() const override {
return Defaults::Entities::ENEMY_RADIUS;
}
@@ -58,7 +58,7 @@ class Enemy : public Entities::Entity {
return esta_ && timer_invulnerabilitat_ <= 0.0F;
}
// Getters (API pública sense canvis)
// Getters (API pública sin canvis)
void destruir() { esta_ = false; }
[[nodiscard]] float get_drotacio() const { return drotacio_; }
[[nodiscard]] Vec2 getVelocityVector() const {
@@ -90,8 +90,8 @@ class Enemy : public Entities::Entity {
private:
// Membres específics d'Enemy (heretats: renderer_, shape_, center_, angle_, brightness_)
float velocity_;
float drotacio_; // Delta rotació visual (rad/s)
float rotacio_; // Rotació visual acumulada
float drotacio_; // Delta rotación visual (rad/s)
float rotacio_; // Rotación visual acumulada
bool esta_;
// [NEW] Enemy type and configuration
@@ -101,9 +101,9 @@ class Enemy : public Entities::Entity {
EnemyAnimation animacio_;
// [NEW] Behavior state (type-specific)
float tracking_timer_; // For Quadrat: time since last angle update
float tracking_timer_; // For Cuadrado: time since last angle update
const Vec2* ship_position_; // Pointer to ship position (for tracking)
float tracking_strength_; // For Quadrat: tracking intensity (0.0-1.5), default 0.5
float tracking_strength_; // For Cuadrado: tracking intensity (0.0-1.5), default 0.5
// [NEW] Invulnerability state
float timer_invulnerabilitat_; // Countdown timer (seconds), 0.0f = vulnerable
+19 -19
View File
@@ -1,6 +1,6 @@
// ship.cpp - Implementació de la nave del player
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#include "game/entities/ship.hpp"
@@ -25,10 +25,10 @@ Ship::Ship(SDL_Renderer* renderer, const char* shape_file)
velocity_(0.0F),
is_hit_(false),
invulnerable_timer_(0.0F) {
// [NUEVO] Brightness específic per naus
// [NUEVO] Brightness específic per naves
brightness_ = Defaults::Brightness::NAU;
// [NUEVO] Carregar shape compartida des de fitxer
// [NUEVO] Carregar shape compartida desde file
shape_ = Graphics::ShapeLoader::load(shape_file);
if (!shape_ || !shape_->isValid()) {
@@ -37,12 +37,12 @@ Ship::Ship(SDL_Renderer* renderer, const char* shape_file)
}
void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
// Inicialització de la ship (triangle)
// Inicialización de la ship (triangle)
// Basat en el codi Pascal original: lines 380-384
// Copiat de joc_asteroides.cpp línies 30-44
// [NUEVO] Ja no cal configurar points polars - la geometria es carrega del
// fitxer Només inicialitzem l'state de la instància
// [NUEVO] Ya no necesario configure points polars - la geometria es carrega del
// file Solo inicialitzem l'state de la instància
// Use custom spawn point if provided, otherwise use center
if (spawn_point != nullptr) {
@@ -72,9 +72,9 @@ void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
}
void Ship::processInput(float delta_time, uint8_t player_id) {
// Processar input continu (com teclapuls() del Pascal original)
// Processar input continu (como teclapuls() del Pascal original)
// Basat en joc_asteroides.cpp línies 66-85
// Només processa input si la ship està viva
// Solo processa input si la ship está viva
if (is_hit_) {
return;
}
@@ -118,7 +118,7 @@ void Ship::processInput(float delta_time, uint8_t player_id) {
}
void Ship::update(float delta_time) {
// Només update si la ship està viva
// Solo update si la ship está viva
if (is_hit_) {
return;
}
@@ -129,12 +129,12 @@ void Ship::update(float delta_time) {
invulnerable_timer_ = std::max(invulnerable_timer_, 0.0F);
}
// Aplicar física (moviment + fricció)
// Aplicar física (movement + fricció)
applyPhysics(delta_time);
}
void Ship::draw() const {
// Només draw si la ship està viva
// Solo draw si la ship está viva
if (is_hit_) {
return;
}
@@ -156,9 +156,9 @@ void Ship::draw() const {
return;
}
// Escalar velocity per l'efecte visual (200 px/s → ~6 px d'efecte)
// Escalar velocity per l'efecte visual (200 px/s → ~6 px de efecte)
// El codi Pascal original sumava velocity (0-6) al radi per donar
// sensació de "empenta". Ara velocity està en px/s (0-200).
// sensació de "empenta". Ara velocity está en px/s (0-200).
// Basat en joc_asteroides.cpp línies 127-134
//
// [NUEVO] Convertir suma de velocitat_visual a scale multiplicativa
@@ -171,12 +171,12 @@ void Ship::draw() const {
}
void Ship::applyPhysics(float delta_time) {
// Aplicar física de moviment
// Aplicar física de movement
// Basat en joc_asteroides.cpp línies 87-113
// Calcular nova posició basada en velocity i angle
// S'usa (angle - PI/2) perquè angle=0 apunta cap amunt, no cap a la dreta
// velocity_ està en px/s, així que multipliquem per delta_time
// Calcular nueva posición basada en velocity i angle
// S'usa (angle - PI/2) perquè angle=0 apunta sin amunt, no hacia la derecha
// velocity_ está en px/s, así que multipliquem per delta_time
float dy =
((velocity_ * delta_time) * std::sin(angle_ - (Constants::PI / 2.0F))) +
center_.y;
@@ -184,7 +184,7 @@ void Ship::applyPhysics(float delta_time) {
((velocity_ * delta_time) * std::cos(angle_ - (Constants::PI / 2.0F))) +
center_.x;
// Boundary checking amb radi de la ship
// Boundary checking con radi de la ship
// CORRECCIÓ: Usar límits segurs i inequalitats inclusives
float min_x;
float max_x;
+7 -7
View File
@@ -1,6 +1,6 @@
// ship.hpp - Classe per a la nave del player
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// ship.hpp - Clase para la nave del player
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#pragma once
#include <SDL3/SDL.h>
@@ -28,7 +28,7 @@ class Ship : public Entities::Entity {
// Override: Interfície d'Entity
[[nodiscard]] bool isActive() const override { return !is_hit_; }
// Override: Interfície de col·lisió
// Override: Interfície de colisión
[[nodiscard]] float getCollisionRadius() const override {
return Defaults::Entities::SHIP_RADIUS;
}
@@ -36,7 +36,7 @@ class Ship : public Entities::Entity {
return !is_hit_ && invulnerable_timer_ <= 0.0F;
}
// Getters (API pública sense canvis)
// Getters (API pública sin canvis)
[[nodiscard]] bool isAlive() const { return !is_hit_; }
[[nodiscard]] bool isHit() const { return is_hit_; }
[[nodiscard]] bool isInvulnerable() const { return invulnerable_timer_ > 0.0F; }
@@ -49,12 +49,12 @@ class Ship : public Entities::Entity {
// Setters
void setCenter(const Vec2& nou_centre) { center_ = nou_centre; }
// Col·lisions (Fase 10)
// Colisiones (Fase 10)
void markHit() { is_hit_ = true; }
private:
// Membres específics de Ship (heretats: renderer_, shape_, center_, angle_, brightness_)
float velocity_; // Velocitat (px/s)
float velocity_; // Velocidad (px/s)
bool is_hit_;
float invulnerable_timer_; // 0.0f = vulnerable, >0.0f = invulnerable
+21 -21
View File
@@ -135,8 +135,8 @@ static const std::unordered_map<int, std::string> BUTTON_TO_STRING = {
{SDL_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN"},
{SDL_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT"},
{SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT"},
{100, "L2_AS_BUTTON"}, // Trigger L2 com a botó digital
{101, "R2_AS_BUTTON"} // Trigger R2 com a botó digital
{100, "L2_AS_BUTTON"}, // Trigger L2 como a botó digital
{101, "R2_AS_BUTTON"} // Trigger R2 como a botó digital
};
// Mapa invers: string a botó de gamepad
@@ -178,7 +178,7 @@ static auto stringToButton(const std::string& str) -> int {
// ========== FI FUNCIONS AUXILIARS ==========
// Inicialitzar opcions amb valors per defecte de Defaults::
// Inicialitzar opciones con valors per defecte de Defaults::
void init() {
#ifdef _DEBUG
console = true;
@@ -219,10 +219,10 @@ void init() {
version = std::string(Project::VERSION);
}
// Establir la ruta del fitxer de configuració
// Establir la ruta del file de configuración
void setConfigFile(const std::string& path) { config_file_path = path; }
// Funcions auxiliars per load seccions del YAML
// Funciones auxiliars per load seccions del YAML
static void loadWindowConfigFromYaml(const fkyaml::node& yaml) {
if (yaml.contains("window")) {
@@ -372,7 +372,7 @@ static void loadRenderingConfigFromYaml(const fkyaml::node& yaml) {
if (rend.contains("vsync")) {
try {
int val = rend["vsync"].get_value<int>();
// Validar: només 0 o 1
// Validar: solo 0 o 1
rendering.vsync = (val == 0 || val == 1) ? val : Defaults::Rendering::VSYNC_DEFAULT;
} catch (...) {
rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT;
@@ -446,7 +446,7 @@ static void loadAudioConfigFromYaml(const fkyaml::node& yaml) {
}
}
// Carregar controls del player 1 des de YAML
// Carregar controls del player 1 desde YAML
static void loadPlayer1ControlsFromYaml(const fkyaml::node& yaml) {
if (!yaml.contains("player1")) {
return;
@@ -494,7 +494,7 @@ static void loadPlayer1ControlsFromYaml(const fkyaml::node& yaml) {
}
}
// Carregar controls del player 2 des de YAML
// Carregar controls del player 2 desde YAML
static void loadPlayer2ControlsFromYaml(const fkyaml::node& yaml) {
if (!yaml.contains("player2")) {
return;
@@ -542,22 +542,22 @@ static void loadPlayer2ControlsFromYaml(const fkyaml::node& yaml) {
}
}
// Carregar configuració des del fitxer YAML
// Carregar configuración des del file YAML
auto loadFromFile() -> bool {
const std::string CONFIG_VERSION = std::string(Project::VERSION);
std::ifstream file(config_file_path);
if (!file.good()) {
// El fitxer no existeix → crear-ne un de nou amb valors per defecte
// El file no existeix → crear-ne un de nuevo con valors per defecte
if (console) {
std::cout << "Fitxer de config no trobat, creant-ne un de nou: "
std::cout << "Archivo de config no trobat, creant-ne un de nuevo: "
<< config_file_path << '\n';
}
saveToFile();
return true;
}
// Llegir tot el contingut del fitxer
// Llegir todo el contingut del file
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
@@ -566,15 +566,15 @@ auto loadFromFile() -> bool {
// Parsejar YAML
auto yaml = fkyaml::node::deserialize(content);
// Validar versió
// Validar versión
if (yaml.contains("version")) {
version = yaml["version"].get_value<std::string>();
}
if (CONFIG_VERSION != version) {
// Versió incompatible → regenerar config
// Versión incompatible → regenerar config
if (console) {
std::cout << "Versió de config incompatible (esperada: "
std::cout << "Versión de config incompatible (esperada: "
<< CONFIG_VERSION << ", trobada: " << version
<< "), regenerant config\n";
}
@@ -593,7 +593,7 @@ auto loadFromFile() -> bool {
loadPlayer2ControlsFromYaml(yaml);
if (console) {
std::cout << "Config carregada correctament des de: " << config_file_path
std::cout << "Config carregada correctament desde: " << config_file_path
<< '\n';
}
@@ -603,7 +603,7 @@ auto loadFromFile() -> bool {
// Error de parsejat YAML → regenerar config
if (console) {
std::cerr << "Error parsejant YAML: " << e.what() << '\n';
std::cerr << "Creant config nou amb valors per defecte\n";
std::cerr << "Creant config nuevo con valors per defecte\n";
}
init();
saveToFile();
@@ -645,19 +645,19 @@ static void savePlayer2ControlsToYaml(std::ofstream& file) {
file << " gamepad_name: \"" << player2.gamepad_name << "\" # Buit = segon disponible\n\n";
}
// Guardar configuració al fitxer YAML
// Guardar configuración al file YAML
auto saveToFile() -> bool {
std::ofstream file(config_file_path);
if (!file.is_open()) {
if (console) {
std::cerr << "No s'ha pogut obrir el fitxer de config per escriure: "
std::cerr << "No s'ha pogut obrir el file de config per escriure: "
<< config_file_path << '\n';
}
return false;
}
// Escriure manualment per controlar format i comentaris
file << "# Orni Attack - Fitxer de Configuració\n";
file << "# Orni Attack - Archivo de Configuración\n";
file << "# Auto-generat. Les edicions manuals es preserven si són "
"vàlides.\n\n";
@@ -670,7 +670,7 @@ auto saveToFile() -> bool {
file << " fullscreen: " << (window.fullscreen ? "true" : "false") << "\n";
file << " zoom_factor: " << window.zoom_factor << " # 0.5x-max (0.1 increments)\n\n";
file << "# FÍSICA (tots els valors en px/s, rad/s, etc.)\n";
file << "# FÍSICA (todos los valors en px/s, rad/s, etc.)\n";
file << "physics:\n";
file << " rotation_speed: " << physics.rotation_speed << " # rad/s\n";
file << " acceleration: " << physics.acceleration << " # px/s²\n";
+6 -6
View File
@@ -6,7 +6,7 @@
namespace Options {
// Estructures de configuració
// Estructures de configuración
struct Window {
int width{640};
@@ -75,7 +75,7 @@ struct PlayerControls {
// Variables globals (inline per evitar ODR violations)
inline std::string version{}; // Versió del config per validació
inline std::string version{}; // Versión del config per validació
inline bool console{false}; // Eixida de debug
inline Window window{};
inline Physics physics{};
@@ -104,17 +104,17 @@ inline PlayerControls player2{
.gamepad_name = "" // Segon gamepad disponible
};
// Per compatibilitat amb pollo (no utilitzat en orni, però necessari per Input)
// Per compatibilitat con pollo (no utilitzat en orni, pero necessari per Input)
inline KeyboardControls keyboard_controls{};
inline GamepadControls gamepad_controls{};
inline std::string config_file_path{}; // Establert per setConfigFile()
// Funcions públiques
// Funciones públiques
void init(); // Inicialitzar amb valors per defecte
void init(); // Inicialitzar con valors per defecte
void setConfigFile(
const std::string& path); // Establir ruta del fitxer de config
const std::string& path); // Establir ruta del file de config
auto loadFromFile() -> bool; // Carregar config YAML
auto saveToFile() -> bool; // Guardar config YAML
+89 -89
View File
@@ -1,6 +1,6 @@
// escena_joc.cpp - Implementació de la lògica del joc
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// game_scene.cpp - Implementació de la lógica del juego
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#include "game_scene.hpp"
@@ -34,39 +34,39 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
floating_score_manager_(sdl.getRenderer()),
text_(sdl.getRenderer()),
init_hud_rect_sound_played_(false) {
// Recuperar configuració de match des del context
// Recuperar configuración de match des del context
match_config_ = context_.getMatchConfig();
// Debug output de la configuració
std::cout << "[GameScene] Configuració de match - P1: "
// Debug output de la configuración
std::cout << "[GameScene] Configuración de match - P1: "
<< (match_config_.jugador1_actiu ? "ACTIU" : "INACTIU")
<< ", P2: "
<< (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU")
<< '\n';
// Consumir opcions (preparació per MODE_DEMO futur)
// Consumir opciones (preparació per MODE_DEMO futur)
auto option = context_.consumeOption();
(void)option; // Suprimir warning de variable no usada
// Inicialitzar naus amb renderer (P1=ship.shp, P2=ship2.shp)
// Inicialitzar naves con renderer (P1=ship.shp, P2=ship2.shp)
ships_[0] = Ship(sdl.getRenderer(), "ship.shp"); // Jugador 1: nave estàndar
ships_[1] = Ship(sdl.getRenderer(), "ship2.shp"); // Jugador 2: interceptor amb ales
ships_[1] = Ship(sdl.getRenderer(), "ship2.shp"); // Jugador 2: interceptor con ales
// Inicialitzar bales amb renderer
// Inicialitzar balas con renderer
for (auto& bullet : bullets_) {
bullet = Bullet(sdl.getRenderer());
}
// Inicialitzar enemics amb renderer
// Inicialitzar enemigos con renderer
for (auto& enemy : enemies_) {
enemy = Enemy(sdl.getRenderer());
}
}
void GameScene::run() {
std::cout << "SceneType Joc: Inicialitzant...\n";
std::cout << "SceneType Juego: Inicialitzant...\n";
// Inicialitzar state del joc
// Inicialitzar state del juego
init();
SDL_Event event;
@@ -78,16 +78,16 @@ void GameScene::run() {
float delta_time = (current_time - last_time) / 1000.0F;
last_time = current_time;
// Limitar delta_time per evitar grans salts
// Limitar delta_time per evitar grandes salts
delta_time = std::min(delta_time, 0.05F);
// Actualitzar comptador de FPS
// Actualitzar counter de FPS
sdl_.updateFPS(delta_time);
// Actualitzar visibilitat del cursor (auto-ocultar)
Mouse::updateCursorVisibility();
// Actualitzar sistema d'input ABANS del event loop
// Actualitzar sistema de input ABANS del event loop
Input::get()->update();
// Processar events SDL
@@ -101,29 +101,29 @@ void GameScene::run() {
GlobalEvents::handle(event, sdl_, context_);
}
// Actualitzar física del joc amb delta_time real
// Actualitzar física del juego con delta_time real
update(delta_time);
// Actualitzar sistema d'audio
// Actualitzar sistema de audio
Audio::update();
// Actualitzar colors oscil·lats
sdl_.updateColors(delta_time);
// Netejar pantalla (usa color oscil·lat)
sdl_.neteja(0, 0, 0);
sdl_.clear(0, 0, 0);
// Actualitzar context de renderitzat (factor d'scale global)
// Actualitzar context de renderizado (factor de scale global)
sdl_.updateRenderingContext();
// Dibuixar joc
// Dibuixar juego
draw();
// Presentar renderer (swap buffers)
sdl_.presenta();
sdl_.present();
}
std::cout << "SceneType Joc: Finalitzant...\n";
std::cout << "SceneType Juego: Finalitzant...\n";
}
void GameScene::init() {
@@ -145,7 +145,7 @@ void GameScene::init() {
stage_manager_->init();
// [NEW] Set ship position reference for safe spawn (P1 for now, TODO: dual tracking)
stage_manager_->get_spawn_controller().set_ship_position(&ships_[0].getCenter());
stage_manager_->getSpawnController().set_ship_position(&ships_[0].getCenter());
// Inicialitzar timers de muerte per player
hit_timer_per_player_[0] = 0.0F;
@@ -165,12 +165,12 @@ void GameScene::init() {
score_per_player_[1] = 0;
floating_score_manager_.reset();
// DEPRECATED: spawn_position_ ja no s'usa, es calcula dinàmicament amb obtenir_punt_spawn(player_id)
// DEPRECATED: spawn_position_ ya no s'usa, es calcula dinàmicament con obtenir_punt_spawn(player_id)
// const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
// spawn_position_.x = zona.x + zona.w * 0.5f;
// spawn_position_.y = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO;
// Inicialitzar naus segons configuració (només jugadors active)
// Inicialitzar naves segons configuración (solo jugadors active)
for (uint8_t i = 0; i < 2; i++) {
bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
@@ -180,10 +180,10 @@ void GameScene::init() {
ships_[i].init(&spawn_pos, false); // No invulnerability at start
std::cout << "[GameScene] Jugador " << (i + 1) << " inicialitzat\n";
} else {
// Jugador inactiu: marcar com a mort permanent
// Jugador inactiu: marcar como a mort permanent
ships_[i].markHit();
hit_timer_per_player_[i] = 999.0F; // Valor sentinella (permanent inactiu)
lives_per_player_[i] = 0; // Sense vides
lives_per_player_[i] = 0; // Sin vides
std::cout << "[GameScene] Jugador " << (i + 1) << " inactiu\n";
}
}
@@ -195,16 +195,16 @@ void GameScene::init() {
// DON'T call enemy.init() here - stage system handles spawning
}
// Inicialitzar bales (now 6 instead of 3)
// Inicialitzar balas (now 6 instead of 3)
for (auto& bullet : bullets_) {
bullet.init();
}
// [ELIMINAT] Iniciar música de joc (ara es gestiona en stage_manager)
// La música s'inicia quan es transiciona de INIT_HUD a LEVEL_START
// [ELIMINAT] Iniciar música de juego (ara es gestiona en stage_manager)
// La música s'inicia cuando es transiciona de INIT_HUD a LEVEL_START
// Audio::get()->playMusic("game.ogg");
// Reset flag de sons d'animació
// Reset flag de sons de animación
init_hud_rect_sound_played_ = false;
}
@@ -213,14 +213,14 @@ void GameScene::update(float delta_time) {
if (game_over_state_ == GameOverState::NONE) {
auto* input = Input::get();
// Jugador 1 dispara (només si està active)
// Jugador 1 dispara (solo si está active)
if (match_config_.jugador1_actiu) {
if (input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
disparar_bala(0);
}
}
// Jugador 2 dispara (només si està active)
// Jugador 2 dispara (solo si está active)
if (match_config_.jugador2_actiu) {
if (input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
disparar_bala(1);
@@ -287,9 +287,9 @@ void GameScene::update(float delta_time) {
game_over_timer_ -= delta_time;
if (game_over_timer_ <= 0.0F) {
// Aturar música de joc abans de tornar al títol
// Aturar música de juego antes de tornar al título
Audio::get()->stopMusic();
// Transició a pantalla de títol
// Transición a pantalla de título
context_.setNextScene(SceneType::TITLE);
SceneManager::actual = SceneType::TITLE;
return;
@@ -374,8 +374,8 @@ void GameScene::update(float delta_time) {
// Update stage manager timer (pot canviar l'state!)
stage_manager_->update(delta_time);
// [FIX] Si l'state ha canviat durant update(), sortir immediatament
// per evitar recalcular la posició de la ship amb el nou timer
// [FIX] Si l'state ha canviat durante update(), salir immediatament
// per evitar recalcular la posición de la ship con el nuevo timer
if (stage_manager_->get_estat() != StageSystem::EstatStage::INIT_HUD) {
break;
}
@@ -395,7 +395,7 @@ void GameScene::update(float delta_time) {
Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
Defaults::Game::INIT_HUD_SHIP2_RATIO_END);
// [MODIFICAT] Animar AMBAS naus con sus progress respectivos
// [MODIFICAT] Animar AMBAS naves con sus progress respectivos
if (match_config_.jugador1_actiu && ship1_progress < 1.0F) {
Vec2 pos_p1 = calcular_posicio_nau_init_hud(ship1_progress, 0);
ships_[0].setCenter(pos_p1);
@@ -406,8 +406,8 @@ void GameScene::update(float delta_time) {
ships_[1].setCenter(pos_p2);
}
// Una vegada l'animació acaba, permetre control normal
// però mantenir la posició inicial especial fins LEVEL_START
// Una vez l'animación acaba, permetre control normal
// pero mantenir la posición inicial especial hasta LEVEL_START
break;
}
@@ -444,13 +444,13 @@ void GameScene::update(float delta_time) {
case StageSystem::EstatStage::PLAYING: {
// [NEW] Update stage manager (spawns enemies, pause if BOTH dead)
bool pausar_spawn = (hit_timer_per_player_[0] > 0.0F && hit_timer_per_player_[1] > 0.0F);
stage_manager_->get_spawn_controller().update(delta_time, enemies_, pausar_spawn);
bool pause_spawn = (hit_timer_per_player_[0] > 0.0F && hit_timer_per_player_[1] > 0.0F);
stage_manager_->getSpawnController().update(delta_time, enemies_, pause_spawn);
// [NEW] Check stage completion (only when at least one player alive)
bool algun_jugador_viu = (hit_timer_per_player_[0] == 0.0F || hit_timer_per_player_[1] == 0.0F);
if (algun_jugador_viu) {
auto& spawn_ctrl = stage_manager_->get_spawn_controller();
auto& spawn_ctrl = stage_manager_->getSpawnController();
if (spawn_ctrl.tots_enemics_destruits(enemies_)) {
stage_manager_->stage_completat();
Audio::get()->playSound(Defaults::Sound::GOOD_JOB_COMMANDER, Audio::Group::GAME);
@@ -552,7 +552,7 @@ void GameScene::draw() {
constexpr float scale = Defaults::Game::GameOverScreen::TEXT_SCALE;
constexpr float spacing = Defaults::Game::GameOverScreen::TEXT_SPACING;
// Calcular centre de l'àrea de joc usant constants
// Calcular centro de l'àrea de juego usant constants
const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
float centre_x = play_area.x + (play_area.w / 2.0F);
float centre_y = play_area.y + (play_area.h / 2.0F);
@@ -568,7 +568,7 @@ void GameScene::draw() {
switch (state) {
case StageSystem::EstatStage::INIT_HUD: {
// Calcular progrés de cada animació independent
// Calcular progrés de cada animación independent
float timer = stage_manager_->get_timer_transicio();
float total_time = Defaults::Game::INIT_HUD_DURATION;
float global_progress = 1.0F - (timer / total_time);
@@ -596,7 +596,7 @@ void GameScene::draw() {
// Dibuixar elements animats
if (rect_progress > 0.0F) {
// [NOU] Reproduir so quan comença l'animació del rectangle
// [NOU] Reproduir so cuando comença l'animación del rectangle
if (!init_hud_rect_sound_played_) {
Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME);
init_hud_rect_sound_played_ = true;
@@ -609,7 +609,7 @@ void GameScene::draw() {
dibuixar_marcador_animat(score_progress);
}
// [MODIFICAT] Dibuixar naus amb progress independent
// [MODIFICAT] Dibuixar naves con progress independent
if (ship1_progress > 0.0F && match_config_.jugador1_actiu && !ships_[0].isHit()) {
ships_[0].draw();
}
@@ -735,7 +735,7 @@ void GameScene::tocado(uint8_t player_id) {
}
void GameScene::dibuixar_marges() const {
// Dibuixar rectangle de la zona de joc
// Dibuixar rectangle de la zona de juego
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
// Coordenades dels cantons
@@ -755,22 +755,22 @@ void GameScene::dibuixar_marcador() {
// Construir text del marcador
std::string text = buildScoreboard();
// Paràmetres de renderització
// Parámetros de renderització
const float scale = 0.85F;
const float spacing = 0.0F;
// Calcular centre de la zona del marcador
// Calcular centro de la zona del marcador
const SDL_FRect& scoreboard = Defaults::Zones::SCOREBOARD;
float centre_x = scoreboard.w / 2.0F;
float centre_y = scoreboard.y + (scoreboard.h / 2.0F);
// Renderitzar centrat
// Renderizar centrat
text_.renderCentered(text, {.x = centre_x, .y = centre_y}, scale, spacing);
}
void GameScene::dibuixar_marges_animat(float progress) const {
// Animació seqüencial del rectangle amb efecte de "pinzell"
// Dos pinzells comencen al centre superior i baixen pels laterals
// Animación seqüencial del rectangle con efecte de "pinzell"
// Dos pinzells comencen al centro superior i baixen por los laterals
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
@@ -792,11 +792,11 @@ void GameScene::dibuixar_marges_animat(float progress) const {
if (eased_progress > 0.0F) {
float phase1_progress = std::min(eased_progress / PHASE_1_END, 1.0F);
// Línia esquerra: creix des del centre cap a l'esquerra
// Línia izquierda: creix des del centro hacia l'izquierda
int x1_phase1 = static_cast<int>(cx - ((cx - x1) * phase1_progress));
Rendering::linea(sdl_.getRenderer(), cx, y1, x1_phase1, y1);
// Línia dreta: creix des del centre cap a la dreta
// Línia derecha: creix des del centro hacia la derecha
int x2_phase1 = static_cast<int>(cx + ((x2 - cx) * phase1_progress));
Rendering::linea(sdl_.getRenderer(), cx, y1, x2_phase1, y1);
}
@@ -805,11 +805,11 @@ void GameScene::dibuixar_marges_animat(float progress) const {
if (eased_progress > PHASE_1_END) {
float phase2_progress = std::min((eased_progress - PHASE_1_END) / (PHASE_2_END - PHASE_1_END), 1.0F);
// Línia esquerra: creix des de dalt cap a baix
// Línia izquierda: creix desde dalt hacia baix
int y2_phase2 = static_cast<int>(y1 + ((y2 - y1) * phase2_progress));
Rendering::linea(sdl_.getRenderer(), x1, y1, x1, y2_phase2);
// Línia dreta: creix des de dalt cap a baix
// Línia derecha: creix desde dalt hacia baix
Rendering::linea(sdl_.getRenderer(), x2, y1, x2, y2_phase2);
}
@@ -817,46 +817,46 @@ void GameScene::dibuixar_marges_animat(float progress) const {
if (eased_progress > PHASE_2_END) {
float phase3_progress = (eased_progress - PHASE_2_END) / (1.0F - PHASE_2_END);
// Línia esquerra: creix des de l'esquerra cap al centre
// Línia izquierda: creix desde l'izquierda hacia el centro
int x_left_phase3 = static_cast<int>(x1 + ((cx - x1) * phase3_progress));
Rendering::linea(sdl_.getRenderer(), x1, y2, x_left_phase3, y2);
// Línia dreta: creix des de la dreta cap al centre
// Línia derecha: creix desde la derecha hacia el centro
int x_right_phase3 = static_cast<int>(x2 - ((x2 - cx) * phase3_progress));
Rendering::linea(sdl_.getRenderer(), x2, y2, x_right_phase3, y2);
}
}
void GameScene::dibuixar_marcador_animat(float progress) {
// Animació del marcador pujant des de baix amb easing
// Animación del marcador pujant desde baix con easing
// Calcular progrés amb easing
// Calcular progrés con easing
float eased_progress = Easing::ease_out_quad(progress);
// Construir text
std::string text = buildScoreboard();
// Paràmetres
// Parámetros
const float scale = 0.85F;
const float spacing = 0.0F;
// Calcular centre de la zona del marcador
// Calcular centro de la zona del marcador
const SDL_FRect& scoreboard = Defaults::Zones::SCOREBOARD;
float centre_x = scoreboard.w / 2.0F;
float centre_y_final = scoreboard.y + (scoreboard.h / 2.0F);
// Posició Y inicial (offscreen, sota de la pantalla)
// Posición Y inicial (offscreen, sota de la pantalla)
auto centre_y_inicial = static_cast<float>(Defaults::Game::HEIGHT);
// Interpolació amb easing
// Interpolació con easing
float centre_y_animada = centre_y_inicial + ((centre_y_final - centre_y_inicial) * eased_progress);
// Renderitzar centrat en posició animada
// Renderizar centrat en posición animada
text_.renderCentered(text, {.x = centre_x, .y = centre_y_animada}, scale, spacing);
}
Vec2 GameScene::calcular_posicio_nau_init_hud(float progress, uint8_t player_id) const {
// Animació de la ship pujant des de baix amb easing
// Animación de la ship pujant desde baix con easing
// [MODIFICAT] Ambas naves usan ease_out_quad (desfase temporal via INIT/END)
// Aplicar easing (uniforme para ambas naves)
@@ -864,16 +864,16 @@ Vec2 GameScene::calcular_posicio_nau_init_hud(float progress, uint8_t player_id)
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
// Calcular posició final segons player (reutilitza obtenir_punt_spawn)
// Calcular posición final segons player (reutilitza obtenir_punt_spawn)
Vec2 spawn_final = obtenir_punt_spawn(player_id);
float x_final = spawn_final.x;
float y_final = spawn_final.y;
// Y inicial: offscreen, 50px sota la zona de joc
// Y inicial: offscreen, 50px sota la zona de juego
float y_inicial = zona.y + zona.h + 50.0F;
// X no canvia (destí segons player_id)
// Y interpola amb easing
// Y interpola con easing
float y_animada = y_inicial + ((y_final - y_inicial) * eased_progress);
return {.x = x_final, .y = y_animada};
@@ -905,7 +905,7 @@ float GameScene::calcular_progress_rango(float global_progress, float ratio_init
}
std::string GameScene::buildScoreboard() const {
// Puntuació P1 (6 dígits) - mostrar zeros si inactiu
// Puntuación P1 (6 dígits) - mostrar zeros si inactiu
std::string score_p1;
std::string vides_p1;
if (match_config_.jugador1_actiu) {
@@ -919,12 +919,12 @@ std::string GameScene::buildScoreboard() const {
vides_p1 = "00";
}
// Nivell (2 dígits)
// Nivel (2 dígits)
uint8_t stage_num = stage_manager_->get_stage_actual();
std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num)
: std::to_string(stage_num);
// Puntuació P2 (6 dígits) - mostrar zeros si inactiu
// Puntuación P2 (6 dígits) - mostrar zeros si inactiu
std::string score_p2;
std::string vides_p2;
if (match_config_.jugador2_actiu) {
@@ -939,7 +939,7 @@ std::string GameScene::buildScoreboard() const {
}
// Format: "123456 03 LEVEL 01 654321 02"
// Nota: dos espais entre seccions, mantenir ambdós slots sempre visibles
// Nota: dos espais entre seccions, mantenir ambdós slots siempre visibles
return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2;
}
@@ -947,13 +947,13 @@ void GameScene::detectar_col·lisions_bales_enemics() {
// Amplificador per hitbox més generós (115%)
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;
// Velocitat d'explosió reduïda per efecte suau
// Velocidad de explosión reduïda per efecte suau
constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (en lloc de 80.0f per defecte)
// Iterar per totes les bales i enemics
// Iterar per todas las balas i enemigos
for (auto& bullet : bullets_) {
for (auto& enemy : enemies_) {
// Comprovar col·lisió utilitzant la interfície genèrica
// Comprovar colisión utilitzant la interfície genèrica
if (Physics::check_collision(bullet, enemy, AMPLIFIER)) {
// *** COL·LISIÓ DETECTADA ***
@@ -980,17 +980,17 @@ void GameScene::detectar_col·lisions_bales_enemics() {
// 3. Create floating score number
floating_score_manager_.crear(points, pos_enemic);
// 4. Destruir enemy (marca com inactiu)
// 4. Destruir enemy (marca como inactiu)
enemy.destruir();
// 2. Crear explosió de fragments
// 2. Crear explosión de fragments
Vec2 vel_enemic = enemy.getVelocityVector();
debris_manager_.explode(
enemy.getShape(), // Forma vectorial del pentàgon
pos_enemic, // Posició central
0.0F, // Angle (enemy té rotació interna)
enemy.getShape(), // Forma vectorial del pentágono
pos_enemic, // Posición central
0.0F, // Angle (enemy té rotación interna)
1.0F, // Escala normal
VELOCITAT_EXPLOSIO, // 50 px/s (explosió suau)
VELOCITAT_EXPLOSIO, // 50 px/s (explosión suau)
enemy.getBrightness(), // Heredar brightness
vel_enemic, // Heredar velocity
enemy.get_drotacio(), // Heredar velocity angular (trayectorias curvas)
@@ -1000,7 +1000,7 @@ void GameScene::detectar_col·lisions_bales_enemics() {
// 3. Desactivar bullet
bullet.desactivar();
// 4. Eixir del bucle intern (bullet només destrueix 1 enemy)
// 4. Eixir del bucle intern (bullet solo destrueix 1 enemy)
break;
}
}
@@ -1031,7 +1031,7 @@ void GameScene::detectar_col·lisio_naus_enemics() {
continue;
}
// Comprovar col·lisió utilitzant la interfície genèrica
// Comprovar colisión utilitzant la interfície genèrica
if (Physics::check_collision(ships_[i], enemy, AMPLIFIER)) {
tocado(i); // Trigger death sequence for player i
break; // Only one collision per player per frame
@@ -1082,7 +1082,7 @@ void GameScene::detectar_col·lisions_bales_jugadors() {
continue;
}
// Comprovar col·lisió utilitzant la interfície genèrica
// Comprovar colisión utilitzant la interfície genèrica
if (Physics::check_collision(bullet, ships_[player_id], AMPLIFIER)) {
// *** FRIENDLY FIRE HIT ***
@@ -1095,7 +1095,7 @@ void GameScene::detectar_col·lisions_bales_jugadors() {
// Victim loses 1 life
tocado(player_id);
// Attacker gains 1 life (no cap)
// Attacker gains 1 life (no sin)
lives_per_player_[bullet_owner]++;
}
@@ -1187,7 +1187,7 @@ Vec2 GameScene::obtenir_punt_spawn(uint8_t player_id) const {
float x_ratio;
if (match_config_.es_un_jugador()) {
// Un sol player: spawn al centre (50%)
// Un sol player: spawn al centro (50%)
x_ratio = 0.5F;
} else {
// Dos jugadors: spawn a posicions separades
+17 -20
View File
@@ -1,9 +1,8 @@
// escena_joc.hpp - Lògica principal del joc
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
// game_scene.hpp - Lógica principal del juego
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3
#ifndef ESCENA_JOC_HPP
#define ESCENA_JOC_HPP
#pragma once
#include <array>
#include <cstdint>
@@ -31,7 +30,7 @@ enum class GameOverState {
GAME_OVER // Final game over (returning to title)
};
// Classe principal del joc (escena)
// Clase principal del juego (escena)
class GameScene {
public:
explicit GameScene(SDLManager& sdl, SceneManager::SceneContext& context);
@@ -45,13 +44,13 @@ class GameScene {
private:
SDLManager& sdl_;
SceneManager::SceneContext& context_;
GameConfig::MatchConfig match_config_; // Configuració de jugadors active
GameConfig::MatchConfig match_config_; // Configuración de jugadors active
// Efectes visuals
Effects::DebrisManager debris_manager_;
Effects::FloatingScoreManager floating_score_manager_;
// Estat del joc
// Estat del juego
std::array<Ship, 2> ships_; // [0]=P1, [1]=P2
std::array<Enemy, Constants::MAX_ORNIS> enemies_;
std::array<Bullet, Constants::MAX_BALES * 2> bullets_; // 6 balas: P1=[0,1,2], P2=[3,4,5]
@@ -74,16 +73,16 @@ class GameScene {
std::unique_ptr<StageSystem::StageSystemConfig> stage_config_;
std::unique_ptr<StageSystem::StageManager> stage_manager_;
// Control de sons d'animació INIT_HUD
// Control de sons de animación INIT_HUD
bool init_hud_rect_sound_played_; // Flag para evitar repetir sonido del rectángulo
// Funcions privades
// Funciones privades
void tocado(uint8_t player_id);
void detectar_col·lisions_bales_enemics(); // Col·lisions bullet-enemy
void detectar_col·lisions_bales_enemics(); // Colisiones bullet-enemy
void detectar_col·lisio_naus_enemics(); // Ship-enemy collision detection (plural)
void detectar_col·lisions_bales_jugadors(); // Bullet-player collision detection (friendly fire)
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
void dibuixar_marcador(); // Dibuixar marcador de puntuació
void dibuixar_marges() const; // Dibuixar vores de la zona de juego
void dibuixar_marcador(); // Dibuixar marcador de puntuación
void disparar_bala(uint8_t player_id); // Shoot bullet from player
[[nodiscard]] Vec2 obtenir_punt_spawn(uint8_t player_id) const; // Get spawn position for player
@@ -97,16 +96,14 @@ class GameScene {
// [NEW] Stage system helpers
void dibuixar_missatge_stage(const std::string& message);
// [NEW] Funcions d'animació per INIT_HUD
void dibuixar_marges_animat(float progress) const; // Rectangle amb creixement uniforme
void dibuixar_marcador_animat(float progress); // Marcador que puja des de baix
[[nodiscard]] Vec2 calcular_posicio_nau_init_hud(float progress, uint8_t player_id) const; // Posició animada de la ship
// [NEW] Funciones de animación per INIT_HUD
void dibuixar_marges_animat(float progress) const; // Rectangle con creixement uniforme
void dibuixar_marcador_animat(float progress); // Marcador que puja desde baix
[[nodiscard]] Vec2 calcular_posicio_nau_init_hud(float progress, uint8_t player_id) const; // Posición animada de la ship
// [NEW] Función helper del sistema de animación INIT_HUD
[[nodiscard]] float calcular_progress_rango(float global_progress, float ratio_init, float ratio_end) const;
// [NEW] Funció helper del marcador
// [NEW] Función helper del marcador
[[nodiscard]] std::string buildScoreboard() const;
};
#endif // ESCENA_JOC_HPP
+50 -50
View File
@@ -1,4 +1,4 @@
// escena_logo.cpp - Implementació de l'escena logo
// logo_scene.cpp - Implementació de l'escena logo
// © 2025 Port a C++20
#include "logo_scene.hpp"
@@ -22,8 +22,8 @@ using SceneManager::SceneContext;
using SceneType = SceneContext::SceneType;
using Option = SceneContext::Option;
// Helper: calcular el progrés individual d'una lletra
// en funció del progrés global (efecte seqüencial)
// Helper: calcular el progrés individual de una lletra
// en función del progrés global (efecte seqüencial)
static float calcular_progress_letra(size_t letra_index, size_t num_letras, float global_progress, float threshold) {
if (num_letras == 0) {
return 1.0F;
@@ -37,7 +37,7 @@ static float calcular_progress_letra(size_t letra_index, size_t num_letras, floa
// Interpolar progrés
if (global_progress < start) {
return 0.0F; // Encara no ha començat
return 0.0F; // Aún no ha començat
}
if (global_progress >= end) {
return 1.0F; // Completament apareguda
@@ -55,7 +55,7 @@ LogoScene::LogoScene(SDLManager& sdl, SceneContext& context)
temps_des_ultima_explosio_(0.0F) {
std::cout << "SceneType Logo: Inicialitzant...\n";
// Consumir opcions (LOGO no processa opcions actualment)
// Consumir opciones (LOGO no processa opciones actualment)
auto option = context_.consumeOption();
(void)option; // Suprimir warning
@@ -64,9 +64,9 @@ LogoScene::LogoScene(SDLManager& sdl, SceneContext& context)
}
LogoScene::~LogoScene() {
// Aturar tots els sons i la música
// Aturar todos los sons y la música
Audio::get()->stopAllSounds();
std::cout << "SceneType Logo: Sons aturats\n";
std::cout << "SceneType Logo: Sons parados\n";
}
void LogoScene::run() {
@@ -79,16 +79,16 @@ void LogoScene::run() {
float delta_time = (current_time - last_time) / 1000.0F;
last_time = current_time;
// Limitar delta_time per evitar grans salts
// Limitar delta_time per evitar grandes salts
delta_time = std::min(delta_time, 0.05F);
// Actualitzar comptador de FPS
// Actualitzar counter de FPS
sdl_.updateFPS(delta_time);
// Actualitzar visibilitat del cursor (auto-ocultar)
Mouse::updateCursorVisibility();
// Actualitzar sistema d'input ABANS del event loop
// Actualitzar sistema de input ABANS del event loop
Input::get()->update();
// Processar events SDL
@@ -103,17 +103,17 @@ void LogoScene::run() {
continue;
}
// Processar events de l'escena (qualsevol tecla/clic salta al joc)
// Processar events de l'escena (qualsevol tecla/clic salta al juego)
processar_events(event);
}
// Actualitzar lògica
// Actualitzar lógica
update(delta_time);
// Actualitzar colors oscil·lats (efecte verd global)
sdl_.updateColors(delta_time);
// Actualitzar context de renderitzat (factor d'scale global)
// Actualitzar context de renderizado (factor de scale global)
sdl_.updateRenderingContext();
// Dibuixar
@@ -126,8 +126,8 @@ void LogoScene::run() {
void LogoScene::inicialitzar_lletres() {
using namespace Graphics;
// Llista de fitxers .shp (A repetida per a les dues A's)
std::vector<std::string> fitxers = {
// Llista de archivos .shp (A repetida para las dues A's)
std::vector<std::string> archivos = {
"logo/letra_j.shp",
"logo/letra_a.shp",
"logo/letra_i.shp",
@@ -138,13 +138,13 @@ void LogoScene::inicialitzar_lletres() {
"logo/letra_e.shp",
"logo/letra_s.shp"};
// Pas 1: Carregar totes les formes i calcular amplades
// Pas 1: Carregar todas las formes i calcular amplades
float ancho_total = 0.0F;
for (const auto& fitxer : fitxers) {
auto shape = ShapeLoader::load(fitxer);
for (const auto& file : archivos) {
auto shape = ShapeLoader::load(file);
if (!shape || !shape->isValid()) {
std::cerr << "[LogoScene] Error carregant " << fitxer << '\n';
std::cerr << "[LogoScene] Error carregant " << file << '\n';
continue;
}
@@ -161,23 +161,23 @@ void LogoScene::inicialitzar_lletres() {
float ancho_sin_escalar = max_x - min_x;
// IMPORTANT: Escalar ancho i offset amb ESCALA_FINAL
// per que les posicions finals coincideixin amb la mida real de les lletres
// IMPORTANT: Escalar ancho i offset con ESCALA_FINAL
// per que las posicions finals coincideixin con la mida real de las lletres
float ancho = ancho_sin_escalar * ESCALA_FINAL;
float offset_centre = (shape->getCenter().x - min_x) * ESCALA_FINAL;
lletres_.push_back({shape,
{.x = 0.0F, .y = 0.0F}, // Posició es calcularà després
{.x = 0.0F, .y = 0.0F}, // Posición es calcularà después
ancho,
offset_centre});
ancho_total += ancho;
}
// Pas 2: Afegir espaiat entre lletres
// Pas 2: Añadir espaiat entre lletres
ancho_total += ESPAI_ENTRE_LLETRES * (lletres_.size() - 1);
// Pas 3: Calcular posició inicial (centrat horitzontal)
// Pas 3: Calcular posición inicial (centrat horitzontal)
constexpr float PANTALLA_ANCHO = 640.0F;
constexpr float PANTALLA_ALTO = 480.0F;
@@ -188,13 +188,13 @@ void LogoScene::inicialitzar_lletres() {
float x_actual = x_inicial;
for (auto& lletra : lletres_) {
// Posicionar el centre 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
// pot no estar exactament al mig del bounding box
lletra.position.x = x_actual + lletra.offset_centre;
lletra.position.y = y_centre;
// Avançar per a següent lletra
// Avançar para següent lletra
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
}
@@ -206,12 +206,12 @@ void LogoScene::canviar_estat(AnimationState nou_estat) {
estat_actual_ = nou_estat;
temps_estat_actual_ = 0.0F; // Reset time
// Inicialitzar state d'explosió
// Inicialitzar state de explosión
if (nou_estat == AnimationState::EXPLOSION) {
lletra_explosio_index_ = 0;
temps_des_ultima_explosio_ = 0.0F;
// Generar ordre aleatori d'explosions
// Generar ordre aleatori de explosions
ordre_explosio_.clear();
for (size_t i = 0; i < lletres_.size(); i++) {
ordre_explosio_.push_back(i);
@@ -228,14 +228,14 @@ void LogoScene::canviar_estat(AnimationState nou_estat) {
}
bool LogoScene::totes_lletres_completes() const {
// Quan global_progress = 1.0, totes les lletres tenen letra_progress = 1.0
// Cuando global_progress = 1.0, todas las lletres tenen letra_progress = 1.0
return temps_estat_actual_ >= DURACIO_ZOOM;
}
void LogoScene::actualitzar_explosions(float delta_time) {
temps_des_ultima_explosio_ += delta_time;
// Comprovar si és el moment d'explode la següent lletra
// Comprovar si es el moment de explode la següent lletra
if (temps_des_ultima_explosio_ >= DELAY_ENTRE_EXPLOSIONS) {
if (lletra_explosio_index_ < lletres_.size()) {
// Explotar lletra actual (en ordre aleatori)
@@ -244,12 +244,12 @@ void LogoScene::actualitzar_explosions(float delta_time) {
debris_manager_->explode(
lletra.shape, // Forma a explode
lletra.position, // Posició
0.0F, // Angle (sense rotació)
lletra.position, // Posición
0.0F, // Angle (sin rotación)
ESCALA_FINAL, // Escala (lletres a scale final)
VELOCITAT_EXPLOSIO, // Velocitat base
VELOCITAT_EXPLOSIO, // Velocidad base
1.0F, // Brightness màxim (per defecte)
{.x = 0.0F, .y = 0.0F} // Sense velocity (per defecte)
{.x = 0.0F, .y = 0.0F} // Sin velocity (per defecte)
);
std::cout << "[LogoScene] Explota lletra " << lletra_explosio_index_ << "\n";
@@ -258,7 +258,7 @@ void LogoScene::actualitzar_explosions(float delta_time) {
lletra_explosio_index_++;
temps_des_ultima_explosio_ = 0.0F;
} else {
// Totes les lletres han explotat, transició a POST_EXPLOSION
// Todas las lletres han explotat, transición a POST_EXPLOSION
canviar_estat(AnimationState::POST_EXPLOSION);
}
}
@@ -275,7 +275,7 @@ void LogoScene::update(float delta_time) {
break;
case AnimationState::ANIMATION: {
// Reproduir so per cada lletra quan comença a aparèixer
// Reproduir so per cada lletra cuando comença a aparèixer
float global_progress = std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F);
for (size_t i = 0; i < lletres_.size() && i < so_reproduit_.size(); i++) {
@@ -286,7 +286,7 @@ void LogoScene::update(float delta_time) {
global_progress,
THRESHOLD_LETRA);
// Reproduir so quan la lletra comença a aparèixer (progress > 0)
// Reproduir so cuando la lletra comença a aparèixer (progress > 0)
if (letra_progress > 0.0F) {
Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME);
so_reproduit_[i] = true;
@@ -312,7 +312,7 @@ void LogoScene::update(float delta_time) {
case AnimationState::POST_EXPLOSION:
if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) {
// Transició a pantalla de títol
// Transición a pantalla de título
context_.setNextScene(SceneType::TITLE);
SceneManager::actual = SceneType::TITLE;
}
@@ -325,21 +325,21 @@ void LogoScene::update(float delta_time) {
SceneManager::actual = SceneType::TITLE;
}
// Actualitzar animacions de debris
// Actualitzar animaciones de debris
debris_manager_->update(delta_time);
}
void LogoScene::draw() {
// Fons negre
sdl_.neteja(0, 0, 0);
sdl_.clear(0, 0, 0);
// PRE_ANIMATION: Només pantalla negra
// PRE_ANIMATION: Solo pantalla negra
if (estat_actual_ == AnimationState::PRE_ANIMATION) {
sdl_.presenta();
return; // No renderitzar lletres
sdl_.present();
return; // No renderizar lletres
}
// ANIMATION o POST_ANIMATION: Dibuixar lletres amb animació
// ANIMATION o POST_ANIMATION: Dibuixar lletres con animación
if (estat_actual_ == AnimationState::ANIMATION ||
estat_actual_ == AnimationState::POST_ANIMATION) {
float global_progress =
@@ -383,15 +383,15 @@ void LogoScene::draw() {
}
}
// EXPLOSION: Dibuixar només lletres que encara no han explotat
// EXPLOSION: Dibuixar solo lletres que aún no han explotat
if (estat_actual_ == AnimationState::EXPLOSION) {
// Crear conjunt de lletres ja explotades
// Crear conjunt de lletres ya explotades
std::set<size_t> explotades;
for (size_t i = 0; i < lletra_explosio_index_; i++) {
explotades.insert(ordre_explosio_[i]);
}
// Dibuixar només lletres que NO han explotat
// Dibuixar solo lletres que NO han explotat
for (size_t i = 0; i < lletres_.size(); i++) {
if (!explotades.contains(i)) {
const auto& lletra = lletres_[i];
@@ -407,12 +407,12 @@ void LogoScene::draw() {
}
}
// POST_EXPLOSION: No draw lletres, només debris (a baix)
// POST_EXPLOSION: No draw lletres, solo debris (a baix)
// Sempre draw debris (si n'hi ha d'active)
// Siempre draw debris (si n'hay de active)
debris_manager_->draw();
sdl_.presenta();
sdl_.present();
}
auto LogoScene::checkSkipButtonPressed() -> bool {
+20 -20
View File
@@ -1,5 +1,5 @@
// escena_logo.hpp - Pantalla d'inici del joc
// Mostra logo JAILGAMES animat amb zoom i salta al joc
// logo_scene.hpp - Pantalla de start del juego
// Muestra logo JAILGAMES animat con zoom i salta al juego
// © 2025 Port a C++20
#pragma once
@@ -25,59 +25,59 @@ class LogoScene {
void run(); // Bucle principal de l'escena
private:
// Màquina d'estats per l'animació
// Màquina de estats per l'animación
enum class AnimationState {
PRE_ANIMATION, // Pantalla negra inicial
ANIMATION, // Animació de zoom de lletres
ANIMATION, // Animación de zoom de lletres
POST_ANIMATION, // Logo complet visible
EXPLOSION, // Explosió seqüencial de lletres
POST_EXPLOSION // Espera després de l'última explosió
EXPLOSION, // Explosión seqüencial de lletres
POST_EXPLOSION // Espera después de l'última explosión
};
SDLManager& sdl_;
SceneManager::SceneContext& context_;
AnimationState estat_actual_; // Estat actual de la màquina
float
temps_estat_actual_; // Temps en l'state actual (reset en cada transició)
temps_estat_actual_; // Temps en l'state actual (reset en cada transición)
// Gestor de fragments d'explosions
// Gestor de fragments de explosions
std::unique_ptr<Effects::DebrisManager> debris_manager_;
// Seguiment d'explosions seqüencials
// Seguiment de explosions seqüencials
size_t lletra_explosio_index_; // Índex de la següent lletra a explode
float temps_des_ultima_explosio_; // Temps des de l'última explosió
std::vector<size_t> ordre_explosio_; // Ordre aleatori d'índexs de lletres
float temps_des_ultima_explosio_; // Temps desde l'última explosión
std::vector<size_t> ordre_explosio_; // Ordre aleatori de índexs de lletres
// Estructura per a cada lletra del logo
// Estructura para cada lletra del logo
struct LetraLogo {
std::shared_ptr<Graphics::Shape> shape;
Vec2 position; // Posició final en pantalla
Vec2 position; // Posición final en pantalla
float ancho; // Ancho del bounding box
float offset_centre; // Distància de min_x a shape_centre.x
float offset_centre; // Distancia de min_x a shape_centre.x
};
std::vector<LetraLogo> lletres_; // 9 lletres: J-A-I-L-G-A-M-E-S
// Seguiment de sons de lletres (evitar reproduccions repetides)
std::array<bool, 9> so_reproduit_; // Track si cada lletra ja ha reproduit el so
std::array<bool, 9> so_reproduit_; // Track si cada lletra ya ha reproduit el so
// Constants d'animació
// Constants de animación
static constexpr float DURACIO_PRE = 1.5F; // Duració PRE_ANIMATION (pantalla negra)
static constexpr float DURACIO_ZOOM = 4.0F; // Duració del zoom (segons)
static constexpr float DURACIO_POST_ANIMATION = 3.0F; // Duració POST_ANIMATION (logo complet)
static constexpr float DURACIO_POST_EXPLOSION = 3.0F; // Duració POST_EXPLOSION (espera final)
static constexpr float DELAY_ENTRE_EXPLOSIONS = 0.1F; // Temps entre explosions de lletres
static constexpr float VELOCITAT_EXPLOSIO = 240.0F; // Velocitat base fragments (px/s)
static constexpr float VELOCITAT_EXPLOSIO = 240.0F; // Velocidad base fragments (px/s)
static constexpr float ESCALA_INICIAL = 0.1F; // Escala inicial (10%)
static constexpr float ESCALA_FINAL = 0.8F; // Escala final (80%)
static constexpr float ESPAI_ENTRE_LLETRES = 10.0F; // Espaiat entre lletres
// Constants d'animació 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 ORIGEN_ZOOM_X = Defaults::Game::WIDTH * 0.5F; // Vec2 inicial X del zoom
static constexpr float ORIGEN_ZOOM_Y = Defaults::Game::HEIGHT * 0.4F; // Vec2 inicial Y del zoom
// Mètodes privats
// Métodos privats
void inicialitzar_lletres();
void update(float delta_time);
void actualitzar_explosions(float delta_time);
@@ -85,7 +85,7 @@ class LogoScene {
void processar_events(const SDL_Event& event);
auto checkSkipButtonPressed() -> bool;
// Mètodes de gestió d'estats
// Métodos de gestió de estats
void canviar_estat(AnimationState nou_estat);
[[nodiscard]] bool totes_lletres_completes() const;
};
+70 -70
View File
@@ -1,4 +1,4 @@
// escena_titol.cpp - Implementació de l'escena de títol
// title_scene.cpp - Implementació de l'escena de título
// © 2025 Port a C++20
#include "title_scene.hpp"
@@ -36,16 +36,16 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
factor_lerp_(0.0F) {
std::cout << "SceneType Titol: Inicialitzant...\n";
// Inicialitzar configuració de match (cap player active per defecte)
// Inicialitzar configuración de match (sin player active per defecte)
match_config_.jugador1_actiu = false;
match_config_.jugador2_actiu = false;
match_config_.mode = GameConfig::Mode::NORMAL;
// Processar opció del context
// Processar opción del context
auto option = context_.consumeOption();
if (option == Option::JUMP_TO_TITLE_MAIN) {
std::cout << "SceneType Titol: Opció JUMP_TO_TITLE_MAIN activada\n";
std::cout << "SceneType Titol: Opción JUMP_TO_TITLE_MAIN activada\n";
estat_actual_ = TitleState::MAIN;
temps_estat_main_ = 0.0F;
}
@@ -68,16 +68,16 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
150 // densitat: 150 estrelles (50 per capa)
);
// Brightness depèn de l'opció
// Brightness depèn de l'opción
if (estat_actual_ == TitleState::MAIN) {
// Si saltem a MAIN, starfield instantàniament brillant
starfield_->set_brightness(BRIGHTNESS_STARFIELD);
} else {
// Flux normal: comença amb brightness 0.0 per fade-in
// Flux normal: comença con brightness 0.0 per fade-in
starfield_->set_brightness(0.0F);
}
// Inicialitzar animador de naus 3D
// Inicialitzar animador de naves 3D
ship_animator_ = std::make_unique<Title::ShipAnimator>(sdl_.getRenderer());
ship_animator_->init();
@@ -90,17 +90,17 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
ship_animator_->set_visible(false);
}
// Inicialitzar lletres del títol "ORNI ATTACK!"
// Inicialitzar lletres del título "ORNI ATTACK!"
inicialitzar_titol();
// Iniciar música de títol si no està sonant
// Iniciar música de título si no está sonant
if (Audio::get()->getMusicState() != Audio::MusicState::PLAYING) {
Audio::get()->playMusic("title.ogg");
}
}
TitleScene::~TitleScene() {
// Aturar música de títol quan es destrueix l'escena
// Aturar música de título cuando es destrueix l'escena
Audio::get()->stopMusic();
}
@@ -117,10 +117,10 @@ void TitleScene::inicialitzar_titol() {
// Pas 1: Carregar formes i calcular amplades per "ORNI"
float ancho_total_orni = 0.0F;
for (const auto& fitxer : fitxers_orni) {
auto shape = ShapeLoader::load(fitxer);
for (const auto& file : fitxers_orni) {
auto shape = ShapeLoader::load(file);
if (!shape || !shape->isValid()) {
std::cerr << "[TitleScene] Error carregant " << fitxer << '\n';
std::cerr << "[TitleScene] Error carregant " << file << '\n';
continue;
}
@@ -142,7 +142,7 @@ void TitleScene::inicialitzar_titol() {
float ancho_sin_escalar = max_x - min_x;
float altura_sin_escalar = max_y - min_y;
// Escalar ancho, altura i offset amb LOGO_SCALE
// Escalar ancho, altura i offset con LOGO_SCALE
float ancho = ancho_sin_escalar * Defaults::Title::Layout::LOGO_SCALE;
float altura = altura_sin_escalar * Defaults::Title::Layout::LOGO_SCALE;
float offset_centre = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
@@ -152,10 +152,10 @@ void TitleScene::inicialitzar_titol() {
ancho_total_orni += ancho;
}
// Afegir espaiat entre lletres
// Añadir espaiat entre lletres
ancho_total_orni += ESPAI_ENTRE_LLETRES * (lletres_orni_.size() - 1);
// Calcular posició inicial (centrat horitzontal) per "ORNI"
// Calcular posición inicial (centrat horitzontal) per "ORNI"
float x_inicial_orni = (Defaults::Game::WIDTH - ancho_total_orni) / 2.0F;
float x_actual = x_inicial_orni;
@@ -168,8 +168,8 @@ void TitleScene::inicialitzar_titol() {
std::cout << "[TitleScene] Línia 1 (ORNI): " << lletres_orni_.size()
<< " lletres, ancho total: " << ancho_total_orni << " px\n";
// === Calcular posició Y dinàmica per "ATTACK!" ===
// Totes les lletres ORNI tenen la mateixa altura, utilitzem la primera
// === Calcular posición Y dinàmica per "ATTACK!" ===
// Todas las lletres ORNI tenen la misma altura, utilitzem la primera
float altura_orni = lletres_orni_.empty() ? 50.0F : lletres_orni_[0].altura;
float y_orni = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
float separacion_lineas = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_LINE_SPACING;
@@ -191,10 +191,10 @@ void TitleScene::inicialitzar_titol() {
// Pas 1: Carregar formes i calcular amplades per "ATTACK!"
float ancho_total_attack = 0.0F;
for (const auto& fitxer : fitxers_attack) {
auto shape = ShapeLoader::load(fitxer);
for (const auto& file : fitxers_attack) {
auto shape = ShapeLoader::load(file);
if (!shape || !shape->isValid()) {
std::cerr << "[TitleScene] Error carregant " << fitxer << '\n';
std::cerr << "[TitleScene] Error carregant " << file << '\n';
continue;
}
@@ -216,7 +216,7 @@ void TitleScene::inicialitzar_titol() {
float ancho_sin_escalar = max_x - min_x;
float altura_sin_escalar = max_y - min_y;
// Escalar ancho, altura i offset amb LOGO_SCALE
// Escalar ancho, altura i offset con LOGO_SCALE
float ancho = ancho_sin_escalar * Defaults::Title::Layout::LOGO_SCALE;
float altura = altura_sin_escalar * Defaults::Title::Layout::LOGO_SCALE;
float offset_centre = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
@@ -226,23 +226,23 @@ void TitleScene::inicialitzar_titol() {
ancho_total_attack += ancho;
}
// Afegir espaiat entre lletres
// Añadir espaiat entre lletres
ancho_total_attack += ESPAI_ENTRE_LLETRES * (lletres_attack_.size() - 1);
// Calcular posició inicial (centrat horitzontal) per "ATTACK!"
// Calcular posición inicial (centrat horitzontal) per "ATTACK!"
float x_inicial_attack = (Defaults::Game::WIDTH - ancho_total_attack) / 2.0F;
x_actual = x_inicial_attack;
for (auto& lletra : lletres_attack_) {
lletra.position.x = x_actual + lletra.offset_centre;
lletra.position.y = y_attack_dinamica_; // Usar posició dinàmica
lletra.position.y = y_attack_dinamica_; // Usar posición dinàmica
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
}
std::cout << "[TitleScene] Línia 2 (ATTACK!): " << lletres_attack_.size()
<< " lletres, ancho total: " << ancho_total_attack << " px\n";
// Guardar posicions originals per l'animació orbital
// Guardar posicions originals per l'animación orbital
posicions_originals_orni_.clear();
for (const auto& lletra : lletres_orni_) {
posicions_originals_orni_.push_back(lletra.position);
@@ -253,7 +253,7 @@ void TitleScene::inicialitzar_titol() {
posicions_originals_attack_.push_back(lletra.position);
}
std::cout << "[TitleScene] Animació: Posicions originals guardades\n";
std::cout << "[TitleScene] Animación: Posicions originals guardades\n";
}
void TitleScene::run() {
@@ -266,16 +266,16 @@ void TitleScene::run() {
float delta_time = (current_time - last_time) / 1000.0F;
last_time = current_time;
// Limitar delta_time per evitar grans salts
// Limitar delta_time per evitar grandes salts
delta_time = std::min(delta_time, 0.05F);
// Actualitzar comptador de FPS
// Actualitzar counter de FPS
sdl_.updateFPS(delta_time);
// Actualitzar visibilitat del cursor (auto-ocultar)
Mouse::updateCursorVisibility();
// Actualitzar sistema d'input ABANS del event loop
// Actualitzar sistema de input ABANS del event loop
Input::get()->update();
// Processar events SDL
@@ -294,38 +294,38 @@ void TitleScene::run() {
processar_events(event);
}
// Actualitzar lògica
// Actualitzar lógica
update(delta_time);
// Actualitzar sistema d'audio
// Actualitzar sistema de audio
Audio::update();
// Actualitzar colors oscil·lats
sdl_.updateColors(delta_time);
// Netejar pantalla
sdl_.neteja(0, 0, 0);
sdl_.clear(0, 0, 0);
// Actualitzar context de renderitzat (factor d'scale global)
// Actualitzar context de renderizado (factor de scale global)
sdl_.updateRenderingContext();
// Dibuixar
draw();
// Presentar renderer (swap buffers)
sdl_.presenta();
sdl_.present();
}
std::cout << "SceneType Titol: Finalitzant...\n";
}
void TitleScene::update(float delta_time) {
// Actualitzar starfield (sempre active)
// Actualitzar starfield (siempre active)
if (starfield_) {
starfield_->update(delta_time);
}
// Actualitzar naus (quan visibles)
// Actualitzar naves (cuando visibles)
if (ship_animator_ &&
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
estat_actual_ == TitleState::STARFIELD ||
@@ -345,11 +345,11 @@ void TitleScene::update(float delta_time) {
float brightness_actual = progress * BRIGHTNESS_STARFIELD;
starfield_->set_brightness(brightness_actual);
// Transició a STARFIELD quan el fade es completa
// Transición a STARFIELD cuando el fade es completa
if (temps_acumulat_ >= DURACIO_FADE_IN) {
estat_actual_ = TitleState::STARFIELD;
temps_acumulat_ = 0.0F; // Reset timer per al següent state
starfield_->set_brightness(BRIGHTNESS_STARFIELD); // Assegurar valor final
starfield_->set_brightness(BRIGHTNESS_STARFIELD); // Assegurar value final
}
break;
}
@@ -360,16 +360,16 @@ void TitleScene::update(float delta_time) {
estat_actual_ = TitleState::MAIN;
temps_estat_main_ = 0.0F; // Reset timer al entrar a MAIN
animacio_activa_ = false; // Comença estàtic
factor_lerp_ = 0.0F; // Sense animació encara
factor_lerp_ = 0.0F; // Sin animación aún
// Naus esperaran ENTRANCE_DELAY abans d'entrar (no iniciar aquí)
// Naves esperaran ENTRANCE_DELAY antes de entrar (no start aquí)
}
break;
case TitleState::MAIN: {
temps_estat_main_ += delta_time;
// Iniciar animació d'entrada de naus després del delay
// Iniciar animación de entrada de naves después del delay
if (temps_estat_main_ >= Defaults::Title::Ships::ENTRANCE_DELAY) {
if (ship_animator_ && !ship_animator_->is_visible()) {
ship_animator_->set_visible(true);
@@ -388,13 +388,13 @@ void TitleScene::update(float delta_time) {
factor_lerp_ = temps_lerp / DURACIO_LERP; // 0.0 → 1.0 linealment
animacio_activa_ = true;
}
// Fase 3: Animació completa (12s+)
// Fase 3: Animación completa (12s+)
else {
factor_lerp_ = 1.0F;
animacio_activa_ = true;
}
// Actualitzar animació del logo
// Actualitzar animación del logo
actualitzar_animacio_logo(delta_time);
break;
}
@@ -402,10 +402,10 @@ void TitleScene::update(float delta_time) {
case TitleState::PLAYER_JOIN_PHASE:
temps_acumulat_ += delta_time;
// Continuar animació orbital durant la transició
// Continuar animación orbital durante la transición
actualitzar_animacio_logo(delta_time);
// [NOU] Continuar comprovant si l'altre player vol unir-se durant la transició ("late join")
// [NOU] Continuar comprovant si l'altre player quiere unir-se durante la transición ("late join")
{
bool p1_actiu_abans = match_config_.jugador1_actiu;
bool p2_actiu_abans = match_config_.jugador2_actiu;
@@ -414,7 +414,7 @@ void TitleScene::update(float delta_time) {
// Updates match_config_ if pressed, logs are in the method
context_.setMatchConfig(match_config_);
// Trigger animació de sortida per la ship que acaba d'unir-se
// Trigger animación de salida per la ship que acaba de unir-se
if (ship_animator_) {
if (match_config_.jugador1_actiu && !p1_actiu_abans) {
ship_animator_->trigger_exit_animation_for_player(1);
@@ -426,10 +426,10 @@ void TitleScene::update(float delta_time) {
}
}
// Reproducir so de START quan el segon player s'uneix
// Reproducir so de START cuando el segon player s'uneix
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
// Reiniciar el timer per allargar el time de transició
// Reiniciar el timer per allargar el time de transición
temps_acumulat_ = 0.0F;
std::cout << "[TitleScene] Segon player s'ha unit - so i timer reiniciats\n";
@@ -437,7 +437,7 @@ void TitleScene::update(float delta_time) {
}
if (temps_acumulat_ >= DURACIO_TRANSITION) {
// Transició a pantalla negra
// Transición a pantalla negra
estat_actual_ = TitleState::BLACK_SCREEN;
temps_acumulat_ = 0.0F;
std::cout << "[TitleScene] Passant a BLACK_SCREEN\n";
@@ -449,7 +449,7 @@ void TitleScene::update(float delta_time) {
// No animation, no input checking - just wait
if (temps_acumulat_ >= DURACIO_BLACK_SCREEN) {
// Transició a escena GAME
// Transición a escena GAME
SceneManager::actual = SceneType::GAME;
std::cout << "[TitleScene] Canviant a escena GAME\n";
}
@@ -464,26 +464,26 @@ void TitleScene::update(float delta_time) {
starfield_->set_brightness(BRIGHTNESS_STARFIELD);
temps_estat_main_ = 0.0F;
// Naus esperaran ENTRANCE_DELAY abans d'entrar (no iniciar aquí)
// Naves esperaran ENTRANCE_DELAY antes de entrar (no start aquí)
}
}
// Verificar boton START para iniciar match desde MAIN
// Verificar boton START para start match desde MAIN
if (estat_actual_ == TitleState::MAIN) {
// Guardar state anterior per detectar qui ha premut START AQUEST frame
bool p1_actiu_abans = match_config_.jugador1_actiu;
bool p2_actiu_abans = match_config_.jugador2_actiu;
if (checkStartGameButtonPressed()) {
// Si START es prem durant el delay (naus encara invisibles), saltar-les a FLOATING
// Si START es prem durante el delay (naves aún invisibles), saltar-las a FLOATING
if (ship_animator_ && !ship_animator_->is_visible()) {
ship_animator_->set_visible(true);
ship_animator_->skip_to_floating_state();
}
// Configurar match abans de canviar d'escena
// Configurar match antes de canviar de escena
context_.setMatchConfig(match_config_);
std::cout << "[TitleScene] Configuració de match - P1: "
std::cout << "[TitleScene] Configuración de match - P1: "
<< (match_config_.jugador1_actiu ? "ACTIU" : "INACTIU")
<< ", P2: "
<< (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU")
@@ -493,7 +493,7 @@ void TitleScene::update(float delta_time) {
estat_actual_ = TitleState::PLAYER_JOIN_PHASE;
temps_acumulat_ = 0.0F;
// Trigger animació de sortida NOMÉS per les naus que han premut START
// Trigger animación de salida NOMÉS per las naves que han premut START
if (ship_animator_) {
if (match_config_.jugador1_actiu && !p1_actiu_abans) {
ship_animator_->trigger_exit_animation_for_player(1);
@@ -512,7 +512,7 @@ void TitleScene::update(float delta_time) {
}
void TitleScene::actualitzar_animacio_logo(float delta_time) {
// Només calcular i aplicar offsets si l'animació està activa
// Solo calcular i aplicar offsets si l'animación está activa
if (animacio_activa_) {
// Acumular time escalat
temps_animacio_ += delta_time * factor_lerp_;
@@ -527,13 +527,13 @@ void TitleScene::actualitzar_animacio_logo(float delta_time) {
float offset_x = amplitude_x_actual * std::sin(2.0F * Defaults::Math::PI * frequency_x_actual * temps_animacio_);
float offset_y = amplitude_y_actual * std::sin((2.0F * Defaults::Math::PI * frequency_y_actual * temps_animacio_) + ORBIT_PHASE_OFFSET);
// Aplicar offset a totes les lletres de "ORNI"
// Aplicar offset a todas las lletres de "ORNI"
for (size_t i = 0; i < lletres_orni_.size(); ++i) {
lletres_orni_[i].position.x = posicions_originals_orni_[i].x + static_cast<int>(std::round(offset_x));
lletres_orni_[i].position.y = posicions_originals_orni_[i].y + static_cast<int>(std::round(offset_y));
}
// Aplicar offset a totes les lletres de "ATTACK!"
// Aplicar offset a todas las lletres de "ATTACK!"
for (size_t i = 0; i < lletres_attack_.size(); ++i) {
lletres_attack_[i].position.x = posicions_originals_attack_[i].x + static_cast<int>(std::round(offset_x));
lletres_attack_[i].position.y = posicions_originals_attack_[i].y + static_cast<int>(std::round(offset_y));
@@ -542,12 +542,12 @@ void TitleScene::actualitzar_animacio_logo(float delta_time) {
}
void TitleScene::draw() {
// Dibuixar starfield de fons (en tots els estats excepte BLACK_SCREEN)
// Dibuixar starfield de fons (en todos los estats excepte BLACK_SCREEN)
if (starfield_ && estat_actual_ != TitleState::BLACK_SCREEN) {
starfield_->draw();
}
// Dibuixar naus (després starfield, abans logo)
// Dibuixar naves (después starfield, antes logo)
if (ship_animator_ &&
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
estat_actual_ == TitleState::STARFIELD ||
@@ -556,15 +556,15 @@ void TitleScene::draw() {
ship_animator_->draw();
}
// En els estats STARFIELD_FADE_IN i STARFIELD, només mostrar starfield (sense text)
// En los estats STARFIELD_FADE_IN i STARFIELD, solo mostrar starfield (sin text)
if (estat_actual_ == TitleState::STARFIELD_FADE_IN || estat_actual_ == TitleState::STARFIELD) {
return;
}
// Estat MAIN i PLAYER_JOIN_PHASE: Dibuixar títol i text (sobre el starfield)
// BLACK_SCREEN: no draw res (fons negre ja està netejat)
// Estat MAIN i PLAYER_JOIN_PHASE: Dibuixar título i text (sobre el starfield)
// BLACK_SCREEN: no draw res (fons negre ya está netejat)
if (estat_actual_ == TitleState::MAIN || estat_actual_ == TitleState::PLAYER_JOIN_PHASE) {
// === Calcular i renderitzar ombra (només si animació activa) ===
// === Calcular i renderizar ombra (solo si animación activa) ===
if (animacio_activa_) {
float temps_shadow = temps_animacio_ - SHADOW_DELAY;
temps_shadow = std::max(temps_shadow, 0.0F); // Evitar time negatiu
@@ -642,14 +642,14 @@ void TitleScene::draw() {
}
// === Text "PRESS START TO PLAY" ===
// En state MAIN: sempre visible
// En state TRANSITION: parpellejant (blink amb sinusoide)
// En state MAIN: siempre visible
// En state TRANSITION: parpellejant (blink con sinusoide)
const float spacing = Defaults::Title::Layout::TEXT_SPACING;
bool mostrar_text = true;
if (estat_actual_ == TitleState::PLAYER_JOIN_PHASE) {
// Parpelleig: sin oscil·la entre -1 i 1, volem ON quan > 0
// Parpelleig: sin oscil·la entre -1 i 1, volem ON cuando > 0
float fase = temps_acumulat_ * BLINK_FREQUENCY * 2.0F * std::numbers::pi_v<float>; // 2π × freq × time
mostrar_text = (std::sin(fase) > 0.0F);
}
@@ -689,7 +689,7 @@ void TitleScene::draw() {
float y_line1 = Defaults::Game::HEIGHT * Defaults::Title::Layout::COPYRIGHT1_POS;
float y_line2 = y_line1 + copy_height + line_spacing; // Línea 2 debajo de línea 1
// Renderitzar línees centrades
// Renderizar línees centrades
float centre_x = Defaults::Game::WIDTH / 2.0F;
text_.renderCentered(copyright_original, {.x = centre_x, .y = y_line1}, escala_copy, spacing);
+31 -31
View File
@@ -1,5 +1,5 @@
// escena_titol.hpp - Pantalla de títol del joc
// Mostra message "PRESS BUTTON TO PLAY" i copyright
// title_scene.hpp - Pantalla de título del juego
// Muestra message "PRESS BUTTON TO PLAY" y copyright
// © 2025 Port a C++20
#pragma once
@@ -32,81 +32,81 @@ class TitleScene {
void run(); // Bucle principal de l'escena
private:
// Màquina d'estats per la pantalla de títol
// Màquina de estats per la pantalla de título
enum class TitleState {
STARFIELD_FADE_IN, // Fade-in del starfield (3.0s)
STARFIELD, // Pantalla amb camp d'estrelles (4.0s)
MAIN, // Pantalla de títol amb text (indefinit, fins START)
PLAYER_JOIN_PHASE, // Fase d'unió de jugadors: fade-out música + text parpellejant (2.5s)
BLACK_SCREEN // Pantalla negra de transició (2.0s)
STARFIELD, // Pantalla con camp de estrelles (4.0s)
MAIN, // Pantalla de título con text (indefinit, hasta START)
PLAYER_JOIN_PHASE, // Fase de unió de jugadors: fade-out música + text parpellejant (2.5s)
BLACK_SCREEN // Pantalla negra de transición (2.0s)
};
// Estructura per emmagatzemar informació de cada lletra del títol
// Estructura per emmagatzemar informació de cada lletra del título
struct LetraLogo {
std::shared_ptr<Graphics::Shape> shape; // Forma vectorial de la lletra
Vec2 position; // Posició en pantalla
float ancho; // Amplada escalada
float altura; // Altura escalada
float offset_centre; // Offset del centre per posicionament
Vec2 position; // Posición en pantalla
float ancho; // Amplada scaled
float altura; // Altura scaled
float offset_centre; // Offset del centro per posicionament
};
SDLManager& sdl_;
SceneManager::SceneContext& context_;
GameConfig::MatchConfig match_config_; // Configuració de jugadors active
GameConfig::MatchConfig match_config_; // Configuración de jugadors active
Graphics::VectorText text_; // Sistema de text vectorial
std::unique_ptr<Graphics::Starfield> starfield_; // Camp d'estrelles de fons
std::unique_ptr<Title::ShipAnimator> ship_animator_; // Naus 3D flotants
std::unique_ptr<Graphics::Starfield> starfield_; // Camp de estrelles de fons
std::unique_ptr<Title::ShipAnimator> ship_animator_; // Naves 3D flotantes
TitleState estat_actual_; // Estat actual de la màquina
float temps_acumulat_; // Temps acumulat per l'state INIT
// Lletres del títol "ORNI ATTACK!"
// Lletres del título "ORNI ATTACK!"
std::vector<LetraLogo> lletres_orni_; // Lletres de "ORNI" (línia 1)
std::vector<LetraLogo> lletres_attack_; // Lletres de "ATTACK!" (línia 2)
float y_attack_dinamica_; // Posició Y calculada dinàmicament per "ATTACK!"
float y_attack_dinamica_; // Posición Y calculada dinàmicament per "ATTACK!"
// Estat d'animació del logo
float temps_animacio_; // Temps acumulat per animació orbital
// Estat de animación del logo
float temps_animacio_; // Temps acumulat per animación orbital
std::vector<Vec2> posicions_originals_orni_; // Posicions originals de "ORNI"
std::vector<Vec2> posicions_originals_attack_; // Posicions originals de "ATTACK!"
// Estat d'arrencada de l'animació
// Estat de arrencada de l'animación
float temps_estat_main_; // Temps acumulat en state MAIN
bool animacio_activa_; // Flag: true quan animació està activa
bool animacio_activa_; // Flag: true cuando animación está activa
float factor_lerp_; // Factor de lerp actual (0.0 → 1.0)
// Constants
static constexpr float BRIGHTNESS_STARFIELD = 1.2F; // Brightness del starfield (>1.0 = més brillant)
static constexpr float DURACIO_FADE_IN = 3.0F; // Duració del fade-in del starfield (1.5 segons)
static constexpr float DURACIO_INIT = 4.0F; // Duració de l'state INIT (2 segons)
static constexpr float DURACIO_TRANSITION = 2.5F; // Duració de la transició (1.5 segons)
static constexpr float DURACIO_TRANSITION = 2.5F; // Duració de la transición (1.5 segons)
static constexpr float ESPAI_ENTRE_LLETRES = 10.0F; // Espai entre lletres
static constexpr float BLINK_FREQUENCY = 3.0F; // Freqüència de parpelleig (3 Hz)
static constexpr float DURACIO_BLACK_SCREEN = 2.0F; // Duració pantalla negra (2 segons)
static constexpr int MUSIC_FADE = 1500; // Duracio del fade de la musica del titol al començar a jugar
// Constants d'animació del logo
// Constants de animación del logo
static constexpr float ORBIT_AMPLITUDE_X = 4.0F; // Amplitud oscil·lació horitzontal (píxels)
static constexpr float ORBIT_AMPLITUDE_Y = 3.0F; // Amplitud oscil·lació vertical (píxels)
static constexpr float ORBIT_FREQUENCY_X = 0.8F; // Velocitat oscil·lació horitzontal (Hz)
static constexpr float ORBIT_FREQUENCY_Y = 1.2F; // Velocitat oscil·lació vertical (Hz)
static constexpr float ORBIT_FREQUENCY_X = 0.8F; // Velocidad oscil·lació horitzontal (Hz)
static constexpr float ORBIT_FREQUENCY_Y = 1.2F; // Velocidad oscil·lació vertical (Hz)
static constexpr float ORBIT_PHASE_OFFSET = 1.57F; // Desfasament entre X i Y (90° per circular)
// Constants d'ombra del logo
// Constants de ombra del logo
static constexpr float SHADOW_DELAY = 0.5F; // Retard temporal de l'ombra (segons)
static constexpr float SHADOW_BRIGHTNESS = 0.4F; // Multiplicador de brightness de l'ombra (0.0-1.0)
static constexpr float SHADOW_OFFSET_X = 2.0F; // Offset espacial X fix (píxels)
static constexpr float SHADOW_OFFSET_Y = 2.0F; // Offset espacial Y fix (píxels)
// Temporització de l'arrencada de l'animació
static constexpr float DELAY_INICI_ANIMACIO = 10.0F; // 10s estàtic abans d'animar
// Temporització de l'arrencada de l'animación
static constexpr float DELAY_INICI_ANIMACIO = 10.0F; // 10s estàtic antes de animar
static constexpr float DURACIO_LERP = 2.0F; // 2s per arribar a amplitud completa
// Mètodes privats
// Métodos privats
void update(float delta_time);
void actualitzar_animacio_logo(float delta_time); // Actualitza l'animació orbital del logo
void actualitzar_animacio_logo(float delta_time); // Actualitza l'animación orbital del logo
void draw();
void processar_events(const SDL_Event& event);
auto checkSkipButtonPressed() -> bool;
auto checkStartGameButtonPressed() -> bool;
void inicialitzar_titol(); // Carrega i posiciona les lletres del títol
void inicialitzar_titol(); // Carrega i posiciona las lletres del título
};
@@ -21,13 +21,13 @@ SpawnController::SpawnController()
index_spawn_actual_(0),
ship_position_(nullptr) {}
void SpawnController::configurar(const StageConfig* config) {
void SpawnController::configure(const StageConfig* config) {
config_ = config;
}
void SpawnController::iniciar() {
void SpawnController::start() {
if (config_ == nullptr) {
std::cerr << "[SpawnController] Error: config_ és null" << '\n';
std::cerr << "[SpawnController] Error: config_ es null" << '\n';
return;
}
@@ -122,7 +122,7 @@ void SpawnController::generar_spawn_events() {
return;
}
for (uint8_t i = 0; i < config_->total_enemics; i++) {
for (uint8_t i = 0; i < config_->total_enemies; i++) {
float spawn_time = config_->config_spawn.delay_inicial +
(i * config_->config_spawn.interval_spawn);
@@ -143,7 +143,7 @@ EnemyType SpawnController::seleccionar_tipus_aleatori() const {
if (std::cmp_less(rand_val, config_->distribucio.pentagon)) {
return EnemyType::PENTAGON;
}
if (rand_val < config_->distribucio.pentagon + config_->distribucio.quadrat) {
if (rand_val < config_->distribucio.pentagon + config_->distribucio.cuadrado) {
return EnemyType::QUADRAT;
}
return EnemyType::MOLINILLO;
@@ -1,4 +1,4 @@
// spawn_controller.hpp - Controlador de spawn d'enemics
// spawn_controller.hpp - Controlador de spawn de enemigos
// © 2025 Orni Attack
#pragma once
@@ -16,8 +16,8 @@ namespace StageSystem {
// Informació de spawn planificat
struct SpawnEvent {
float temps_spawn; // Temps absolut (segons) per spawnejar
EnemyType type; // Tipus d'enemy
bool spawnejat; // Ja s'ha processat?
EnemyType type; // Tipo de enemy
bool spawnejat; // Ya s'ha processat?
};
class SpawnController {
@@ -25,8 +25,8 @@ class SpawnController {
SpawnController();
// Configuration
void configurar(const StageConfig* config); // Set stage config
void iniciar(); // Generate spawn schedule
void configure(const StageConfig* config); // Set stage config
void start(); // Generate spawn schedule
void reset(); // Clear all pending spawns
// Update
+19 -19
View File
@@ -1,4 +1,4 @@
// stage_config.hpp - Estructures de dades per configuració d'stages
// stage_config.hpp - Estructures de dades per configuración de stages
// © 2025 Orni Attack
#pragma once
@@ -10,24 +10,24 @@
namespace StageSystem {
// Tipus de mode de spawn
// Tipo de mode de spawn
enum class ModeSpawn {
PROGRESSIVE, // Spawn progressiu amb intervals
IMMEDIATE, // Tots els enemics de cop
WAVE // Onades de 3-5 enemics (futura extensió)
PROGRESSIVE, // Spawn progressiu con intervals
IMMEDIATE, // Todos los enemigos de cop
WAVE // Onades de 3-5 enemigos (futura extensió)
};
// Configuració de spawn
// Configuración de spawn
struct ConfigSpawn {
ModeSpawn mode;
float delay_inicial; // Segons abans del primer spawn
float delay_inicial; // Segons antes del primer spawn
float interval_spawn; // Segons entre spawns consecutius
};
// Distribució de type d'enemics (percentatges)
// Distribució de type de enemigos (percentatges)
struct DistribucioEnemics {
uint8_t pentagon; // 0-100
uint8_t quadrat; // 0-100
uint8_t cuadrado; // 0-100
uint8_t molinillo; // 0-100
// Suma ha de ser 100, validat en StageLoader
};
@@ -36,20 +36,20 @@ struct DistribucioEnemics {
struct MultiplicadorsDificultat {
float velocity; // 0.5-2.0 típic
float rotation; // 0.5-2.0 típic
float tracking_strength; // 0.0-1.5 (aplicat a Quadrat)
float tracking_strength; // 0.0-1.5 (aplicat a Cuadrado)
};
// Metadades del fitxer YAML
// Metadades del file YAML
struct MetadataStages {
std::string version;
uint8_t total_stages;
std::string descripcio;
};
// Configuració completa d'un stage
// Configuración completa de un stage
struct StageConfig {
uint8_t stage_id; // 1-10
uint8_t total_enemics; // 5-15
uint8_t total_enemies; // 5-15
ConfigSpawn config_spawn;
DistribucioEnemics distribucio;
MultiplicadorsDificultat multiplicadors;
@@ -57,17 +57,17 @@ struct StageConfig {
// Validació
[[nodiscard]] bool es_valid() const {
return stage_id >= 1 && stage_id <= 255 &&
total_enemics > 0 && total_enemics <= 15 &&
distribucio.pentagon + distribucio.quadrat + distribucio.molinillo == 100;
total_enemies > 0 && total_enemies <= 15 &&
distribucio.pentagon + distribucio.cuadrado + distribucio.molinillo == 100;
}
};
// Configuració completa del sistema (carregada des de YAML)
// Configuración completa del sistema (carregada desde YAML)
struct StageSystemConfig {
MetadataStages metadata;
std::vector<StageConfig> stages; // Índex [0] = stage 1
// Obtenir configuració d'un stage específic
// Obtenir configuración de un stage específic
[[nodiscard]] const StageConfig* obte_stage(uint8_t stage_id) const {
if (stage_id < 1 || stage_id > stages.size()) {
return nullptr;
@@ -76,9 +76,9 @@ struct StageSystemConfig {
}
};
// Constants per missatges de transició
// Constants per messages de transición
namespace Constants {
// Pool de missatges per inici de level (selecció aleatòria)
// Pool de messages per start de level (selecció aleatòria)
inline constexpr std::array<const char*, 12> MISSATGES_LEVEL_START = {
"ORNI ALERT!",
"INCOMING ORNIS!",
+13 -13
View File
@@ -1,4 +1,4 @@
// stage_loader.cpp - Implementació del carregador de configuració YAML
// stage_loader.cpp - Implementació del carregador de configuración YAML
// © 2025 Orni Attack
#include "stage_loader.hpp"
@@ -58,7 +58,7 @@ std::unique_ptr<StageSystemConfig> StageLoader::load(const std::string& path) {
}
if (!yaml["stages"].is_sequence()) {
std::cerr << "[StageLoader] Error: 'stages' ha de ser una llista" << '\n';
std::cerr << "[StageLoader] Error: 'stages' ha de ser una list" << '\n';
return nullptr;
}
@@ -70,7 +70,7 @@ std::unique_ptr<StageSystemConfig> StageLoader::load(const std::string& path) {
config->stages.push_back(stage);
}
// Validar configuració
// Validar configuración
if (!validar_config(*config)) {
return nullptr;
}
@@ -115,7 +115,7 @@ bool StageLoader::parse_stage(const fkyaml::node& yaml, StageConfig& stage) {
}
stage.stage_id = yaml["stage_id"].get_value<uint8_t>();
stage.total_enemics = yaml["total_enemies"].get_value<uint8_t>();
stage.total_enemies = yaml["total_enemies"].get_value<uint8_t>();
if (!parse_spawn_config(yaml["spawn_config"], stage.config_spawn)) {
return false;
@@ -129,7 +129,7 @@ bool StageLoader::parse_stage(const fkyaml::node& yaml, StageConfig& stage) {
if (!stage.es_valid()) {
std::cerr << "[StageLoader] Error: stage " << static_cast<int>(stage.stage_id)
<< " no és vàlid" << '\n';
<< " no es vàlid" << '\n';
return false;
}
@@ -162,18 +162,18 @@ bool StageLoader::parse_spawn_config(const fkyaml::node& yaml, ConfigSpawn& conf
bool StageLoader::parse_distribution(const fkyaml::node& yaml, DistribucioEnemics& dist) {
try {
if (!yaml.contains("pentagon") || !yaml.contains("quadrat") ||
if (!yaml.contains("pentagon") || !yaml.contains("cuadrado") ||
!yaml.contains("molinillo")) {
std::cerr << "[StageLoader] Error: enemy_distribution incompleta" << '\n';
return false;
}
dist.pentagon = yaml["pentagon"].get_value<uint8_t>();
dist.quadrat = yaml["quadrat"].get_value<uint8_t>();
dist.cuadrado = yaml["cuadrado"].get_value<uint8_t>();
dist.molinillo = yaml["molinillo"].get_value<uint8_t>();
// Validar que suma 100
int sum = dist.pentagon + dist.quadrat + dist.molinillo;
int sum = dist.pentagon + dist.cuadrado + dist.molinillo;
if (sum != 100) {
std::cerr << "[StageLoader] Error: distribució no suma 100 (suma=" << sum << ")" << '\n';
return false;
@@ -200,13 +200,13 @@ bool StageLoader::parse_multipliers(const fkyaml::node& yaml, MultiplicadorsDifi
// Validar rangs raonables
if (mult.velocity < 0.1F || mult.velocity > 5.0F) {
std::cerr << "[StageLoader] Warning: speed_multiplier fora de rang (0.1-5.0)" << '\n';
std::cerr << "[StageLoader] Warning: speed_multiplier fuera de rang (0.1-5.0)" << '\n';
}
if (mult.rotation < 0.1F || mult.rotation > 5.0F) {
std::cerr << "[StageLoader] Warning: rotation_multiplier fora de rang (0.1-5.0)" << '\n';
std::cerr << "[StageLoader] Warning: rotation_multiplier fuera de rang (0.1-5.0)" << '\n';
}
if (mult.tracking_strength < 0.0F || mult.tracking_strength > 2.0F) {
std::cerr << "[StageLoader] Warning: tracking_strength fora de rang (0.0-2.0)" << '\n';
std::cerr << "[StageLoader] Warning: tracking_strength fuera de rang (0.0-2.0)" << '\n';
}
return true;
@@ -233,13 +233,13 @@ ModeSpawn StageLoader::parse_spawn_mode(const std::string& mode_str) {
bool StageLoader::validar_config(const StageSystemConfig& config) {
if (config.stages.empty()) {
std::cerr << "[StageLoader] Error: cap stage carregat" << '\n';
std::cerr << "[StageLoader] Error: sin stage carregat" << '\n';
return false;
}
if (config.stages.size() != config.metadata.total_stages) {
std::cerr << "[StageLoader] Warning: nombre de stages (" << config.stages.size()
<< ") no coincideix amb metadata.total_stages ("
<< ") no coincideix con metadata.total_stages ("
<< static_cast<int>(config.metadata.total_stages) << ")" << '\n';
}
+3 -3
View File
@@ -1,4 +1,4 @@
// stage_loader.hpp - Carregador de configuració YAML
// stage_loader.hpp - Carregador de configuración YAML
// © 2025 Orni Attack
#pragma once
@@ -13,8 +13,8 @@ namespace StageSystem {
class StageLoader {
public:
// Carregar configuració des de fitxer YAML
// Retorna nullptr si hi ha errors
// Carregar configuración desde file YAML
// Retorna nullptr si hay errors
static std::unique_ptr<StageSystemConfig> load(const std::string& path);
private:
+14 -14
View File
@@ -1,4 +1,4 @@
// stage_manager.cpp - Implementació del gestor d'stages
// stage_manager.cpp - Implementació del gestor de stages
// © 2025 Orni Attack
#include "stage_manager.hpp"
@@ -20,7 +20,7 @@ StageManager::StageManager(const StageSystemConfig* config)
stage_actual_(1),
timer_transicio_(0.0F) {
if (config_ == nullptr) {
std::cerr << "[StageManager] Error: config és null" << '\n';
std::cerr << "[StageManager] Error: config es null" << '\n';
}
}
@@ -33,7 +33,7 @@ void StageManager::init() {
<< '\n';
}
void StageManager::update(float delta_time, bool pausar_spawn) {
void StageManager::update(float delta_time, bool pause_spawn) {
switch (estat_) {
case EstatStage::INIT_HUD:
processar_init_hud(delta_time);
@@ -44,7 +44,7 @@ void StageManager::update(float delta_time, bool pausar_spawn) {
break;
case EstatStage::PLAYING:
processar_playing(delta_time, pausar_spawn);
processar_playing(delta_time, pause_spawn);
break;
case EstatStage::LEVEL_COMPLETED:
@@ -86,14 +86,14 @@ void StageManager::canviar_estat(EstatStage nou_estat) {
size_t index = static_cast<size_t>(std::rand()) % Constants::MISSATGES_LEVEL_START.size();
missatge_level_start_actual_ = Constants::MISSATGES_LEVEL_START[index];
// [NOU] Iniciar música al entrar en LEVEL_START (després de INIT_HUD)
// Només si no està sonant ja (per evitar reset en loops posteriors)
// [NOU] Iniciar música al entrar en LEVEL_START (después de INIT_HUD)
// Solo si no está sonant ya (per evitar reset en loops posteriors)
if (Audio::get()->getMusicState() != Audio::MusicState::PLAYING) {
Audio::get()->playMusic("game.ogg");
}
}
std::cout << "[StageManager] Canvi d'state: ";
std::cout << "[StageManager] Canvi de state: ";
switch (nou_estat) {
case EstatStage::INIT_HUD:
std::cout << "INIT_HUD";
@@ -127,12 +127,12 @@ void StageManager::processar_level_start(float delta_time) {
}
}
void StageManager::processar_playing(float delta_time, bool pausar_spawn) {
// Update spawn controller (pauses when pausar_spawn = true)
void StageManager::processar_playing(float delta_time, bool pause_spawn) {
// Update spawn controller (pauses when pause_spawn = true)
// Note: The actual enemy array update happens in GameScene::update()
// This is just for internal timekeeping
(void)delta_time; // Spawn controller is updated externally
(void)pausar_spawn; // Passed to spawn_controller_.update() by GameScene
(void)pause_spawn; // Passed to spawn_controller_.update() by GameScene
}
void StageManager::processar_level_completed(float delta_time) {
@@ -145,7 +145,7 @@ void StageManager::processar_level_completed(float delta_time) {
// Loop back to stage 1 after final stage
if (stage_actual_ > config_->metadata.total_stages) {
stage_actual_ = 1;
std::cout << "[StageManager] Totes les stages completades! Tornant a stage 1"
std::cout << "[StageManager] Todas las stages completades! Tornant a stage 1"
<< '\n';
}
@@ -164,11 +164,11 @@ void StageManager::carregar_stage(uint8_t stage_id) {
}
// Configure spawn controller
spawn_controller_.configurar(stage_config);
spawn_controller_.iniciar();
spawn_controller_.configure(stage_config);
spawn_controller_.start();
std::cout << "[StageManager] Carregat stage " << static_cast<int>(stage_id) << ": "
<< static_cast<int>(stage_config->total_enemics) << " enemics" << '\n';
<< static_cast<int>(stage_config->total_enemies) << " enemigos" << '\n';
}
} // namespace StageSystem
+6 -6
View File
@@ -1,4 +1,4 @@
// stage_manager.hpp - Gestor d'state i progressió d'stages
// stage_manager.hpp - Gestor de state i progressió de stages
// © 2025 Orni Attack
#pragma once
@@ -13,7 +13,7 @@ namespace StageSystem {
// Estats del stage system
enum class EstatStage {
INIT_HUD, // Animació inicial del HUD (3s)
INIT_HUD, // Animación inicial del HUD (3s)
LEVEL_START, // Pantalla "ENEMY INCOMING" (3s)
PLAYING, // Gameplay normal
LEVEL_COMPLETED // Pantalla "GOOD JOB COMMANDER!" (3s)
@@ -25,7 +25,7 @@ class StageManager {
// Lifecycle
void init(); // Reset to stage 1
void update(float delta_time, bool pausar_spawn = false);
void update(float delta_time, bool pause_spawn = false);
// Stage progression
void stage_completat(); // Call when all enemies destroyed
@@ -39,8 +39,8 @@ class StageManager {
[[nodiscard]] const std::string& get_missatge_level_start() const { return missatge_level_start_actual_; }
// Spawn control (delegate to SpawnController)
SpawnController& get_spawn_controller() { return spawn_controller_; }
[[nodiscard]] const SpawnController& get_spawn_controller() const { return spawn_controller_; }
SpawnController& getSpawnController() { return spawn_controller_; }
[[nodiscard]] const SpawnController& getSpawnController() const { return spawn_controller_; }
private:
const StageSystemConfig* config_; // Non-owning pointer
@@ -55,7 +55,7 @@ class StageManager {
void canviar_estat(EstatStage nou_estat);
void processar_init_hud(float delta_time);
void processar_level_start(float delta_time);
void processar_playing(float delta_time, bool pausar_spawn);
void processar_playing(float delta_time, bool pause_spawn);
void processar_level_completed(float delta_time);
void carregar_stage(uint8_t stage_id);
};
+54 -54
View File
@@ -1,5 +1,5 @@
// ship_animator.cpp - Implementació del sistema d'animació de naus
// © 2025 Port a C++20 amb SDL3
// ship_animator.cpp - Implementació del sistema de animación de naves
// © 2025 Port a C++20 con SDL3
#include "ship_animator.hpp"
@@ -18,9 +18,9 @@ ShipAnimator::ShipAnimator(SDL_Renderer* renderer)
}
void ShipAnimator::init() {
// Carregar formes de naus amb perspectiva pre-calculada
auto forma_p1 = Graphics::ShapeLoader::load("ship_perspective.shp"); // Perspectiva esquerra
auto forma_p2 = Graphics::ShapeLoader::load("ship2_perspective.shp"); // Perspectiva dreta
// Carregar formes de naves con perspectiva pre-calculada
auto forma_p1 = Graphics::ShapeLoader::load("ship_perspective.shp"); // Perspectiva izquierda
auto forma_p2 = Graphics::ShapeLoader::load("ship2_perspective.shp"); // Perspectiva derecha
// Configurar ship P1
ships_[0].player_id = 1;
@@ -60,14 +60,14 @@ void ShipAnimator::draw() const {
continue;
}
// Renderitzar ship (perspectiva ja incorporada a la shape)
// Renderizar ship (perspectiva ya incorporada a la shape)
Rendering::render_shape(
renderer_,
ship.shape,
ship.current_position,
0.0F, // angle (rotació 2D no utilitzada)
0.0F, // angle (rotación 2D no utilitzada)
ship.current_scale,
1.0F, // progress (sempre visible)
1.0F, // progress (siempre visible)
1.0F // brightness (brightness màxima)
);
}
@@ -76,14 +76,14 @@ void ShipAnimator::draw() const {
void ShipAnimator::start_entry_animation() {
using namespace Defaults::Title::Ships;
// Configurar ship P1 per a l'animació d'entrada
// Configurar ship P1 para l'animación de entrada
ships_[0].state = ShipState::ENTERING;
ships_[0].state_time = 0.0F;
ships_[0].initial_position = calcular_posicio_fora_pantalla(CLOCK_8_ANGLE);
ships_[0].current_position = ships_[0].initial_position;
ships_[0].current_scale = ships_[0].initial_scale;
// Configurar ship P2 per a l'animació d'entrada
// Configurar ship P2 para l'animación de entrada
ships_[1].state = ShipState::ENTERING;
ships_[1].state_time = 0.0F;
ships_[1].initial_position = calcular_posicio_fora_pantalla(CLOCK_4_ANGLE);
@@ -92,38 +92,38 @@ void ShipAnimator::start_entry_animation() {
}
void ShipAnimator::trigger_exit_animation() {
// Configurar ambdues naus per a l'animació de sortida
// Configurar ambdues naves para l'animación de salida
for (auto& ship : ships_) {
// Canviar state a EXITING
ship.state = ShipState::EXITING;
ship.state_time = 0.0F;
// Preservar posició actual (pot estar a mig camí si START es prem durant ENTERING)
// Preservar posición actual (pot estar a mig camí si START es prem durante ENTERING)
ship.initial_position = ship.current_position;
// La scale objectiu es preserva per a calcular la interpolació
// (current_scale pot ser diferent si està en ENTERING)
// La scale objetivo es preserva para calcular la interpolació
// (current_scale pot ser diferent si está en ENTERING)
}
}
void ShipAnimator::skip_to_floating_state() {
// Posar ambdues naus directament en state FLOATING
// Posar ambdues naves directament en state FLOATING
for (auto& ship : ships_) {
ship.state = ShipState::FLOATING;
ship.state_time = 0.0F;
ship.oscillation_phase = 0.0F;
// Posar en posició objectiu (sense animació)
// Posar en posición objetivo (sin animación)
ship.current_position = ship.target_position;
ship.current_scale = ship.target_scale;
// NO establir visibilitat aquí - ja ho fa el caller
// (evita fer visibles ambdues naus quan només una ha premut START)
// NO establir visibilitat aquí - ya ho hace el caller
// (evita fer visibles ambdues naves cuando solo una ha premut START)
}
}
bool ShipAnimator::is_visible() const {
// Retorna true si almenys una ship és visible
// Retorna true si almenys una ship es visible
for (const auto& ship : ships_) {
if (ship.visible) {
return true;
@@ -136,16 +136,16 @@ void ShipAnimator::trigger_exit_animation_for_player(int player_id) {
// Trobar la ship del player especificat
for (auto& ship : ships_) {
if (ship.player_id == player_id) {
// Canviar state a EXITING només per aquesta ship
// Canviar state a EXITING solo per esta ship
ship.state = ShipState::EXITING;
ship.state_time = 0.0F;
// Preservar posició actual (pot estar a mig camí si START es prem durant ENTERING)
// Preservar posición actual (pot estar a mig camí si START es prem durante ENTERING)
ship.initial_position = ship.current_position;
// La scale objectiu es preserva per a calcular la interpolació
// (current_scale pot ser diferent si està en ENTERING)
break; // Només una ship per player
// La scale objetivo es preserva para calcular la interpolació
// (current_scale pot ser diferent si está en ENTERING)
break; // Solo una ship per player
}
}
}
@@ -157,67 +157,67 @@ void ShipAnimator::set_visible(bool visible) {
}
bool ShipAnimator::is_animation_complete() const {
// Comprovar si totes les naus són invisibles (han completat l'animació de sortida)
// Comprovar si todas las naves són invisibles (han completat l'animación de salida)
for (const auto& ship : ships_) {
if (ship.visible) {
return false; // Encara hi ha alguna ship visible
return false; // Aún hay alguna ship visible
}
}
return true; // Totes les naus són invisibles
return true; // Todas las naves són invisibles
}
// Mètodes d'animació (stubs)
// Métodos de animación (stubs)
void ShipAnimator::actualitzar_entering(TitleShip& ship, float delta_time) {
using namespace Defaults::Title::Ships;
ship.state_time += delta_time;
// Esperar al delay abans de començar l'animació
// Esperar al delay antes de començar l'animación
if (ship.state_time < ship.entry_delay) {
// Encara en delay: la ship es queda fora de pantalla (posició inicial)
// Aún en delay: la ship es queda fuera de pantalla (posición inicial)
ship.current_position = ship.initial_position;
ship.current_scale = ship.initial_scale;
return;
}
// Càlcul del progrés (restant el delay)
// Cálculo del progrés (restant el delay)
float elapsed = ship.state_time - ship.entry_delay;
float progress = std::min(1.0F, elapsed / ENTRY_DURATION);
// Aplicar easing (ease_out_quad per arribada suau)
float eased_progress = Easing::ease_out_quad(progress);
// Lerp posició (inicial → objectiu)
// Lerp posición (inicial → objetivo)
ship.current_position.x = Easing::lerp(ship.initial_position.x, ship.target_position.x, eased_progress);
ship.current_position.y = Easing::lerp(ship.initial_position.y, ship.target_position.y, eased_progress);
// Lerp scale (gran → normal)
// Lerp scale (grande → normal)
ship.current_scale = Easing::lerp(ship.initial_scale, ship.target_scale, eased_progress);
// Transicionar a FLOATING quan completi
// Transicionar a FLOATING cuando completi
if (elapsed >= ENTRY_DURATION) {
ship.state = ShipState::FLOATING;
ship.state_time = 0.0F;
ship.oscillation_phase = 0.0F; // Reiniciar fase d'oscil·lació
ship.oscillation_phase = 0.0F; // Reiniciar fase de oscil·lació
}
}
void ShipAnimator::actualitzar_floating(TitleShip& ship, float delta_time) {
using namespace Defaults::Title::Ships;
// Actualitzar time i fase d'oscil·lació
// Actualitzar time i fase de oscil·lació
ship.state_time += delta_time;
ship.oscillation_phase += delta_time;
// Oscil·lació sinusoïdal X/Y (paràmetres específics per ship)
// Oscil·lació sinusoïdal X/Y (parámetros específics per ship)
float offset_x = ship.amplitude_x * std::sin(2.0F * Defaults::Math::PI * ship.frequency_x * ship.oscillation_phase);
float offset_y = ship.amplitude_y * std::sin((2.0F * Defaults::Math::PI * ship.frequency_y * ship.oscillation_phase) + FLOAT_PHASE_OFFSET);
// Aplicar oscil·lació a la posició objectiu
// Aplicar oscil·lació a la posición objetivo
ship.current_position.x = ship.target_position.x + offset_x;
ship.current_position.y = ship.target_position.y + offset_y;
// Escala constant (sense "breathing" per ara)
// Escala constant (sin "breathing" per ara)
ship.current_scale = ship.target_scale;
}
@@ -229,27 +229,27 @@ void ShipAnimator::actualitzar_exiting(TitleShip& ship, float delta_time) {
// Calcular progrés (0.0 → 1.0)
float progress = std::min(1.0F, ship.state_time / EXIT_DURATION);
// Aplicar easing (ease_in_quad per acceleració cap al point de fuga)
// Aplicar easing (ease_in_quad per aceleración hacia el point de fuga)
float eased_progress = Easing::ease_in_quad(progress);
// Vec2 de fuga (centre del starfield)
// Vec2 de fuga (centro del starfield)
constexpr Vec2 punt_fuga{.x = VANISHING_POINT_X, .y = VANISHING_POINT_Y};
// Lerp posició cap al point de fuga (preservar posició inicial actual)
// Nota: initial_position conté la posició on estava quan es va activar EXITING
// Lerp posición hacia el point de fuga (preservar posición inicial actual)
// Nota: initial_position conté la posición on estava cuando es va activar EXITING
ship.current_position.x = Easing::lerp(ship.initial_position.x, punt_fuga.x, eased_progress);
ship.current_position.y = Easing::lerp(ship.initial_position.y, punt_fuga.y, eased_progress);
// Escala redueix a 0 (simula Z → infinit)
ship.current_scale = ship.target_scale * (1.0F - eased_progress);
// Marcar invisible quan l'animació completi
// Marcar invisible cuando l'animación completi
if (progress >= 1.0F) {
ship.visible = false;
}
}
// Configuració
// Configuración
void ShipAnimator::configurar_nau_p1(TitleShip& ship) {
using namespace Defaults::Title::Ships;
@@ -260,9 +260,9 @@ void ShipAnimator::configurar_nau_p1(TitleShip& ship) {
// Posicions (clock 8, bottom-left)
ship.target_position = {.x = P1_TARGET_X(), .y = P1_TARGET_Y()};
// Calcular posició inicial (fora de pantalla)
// Calcular posición inicial (fuera de pantalla)
ship.initial_position = calcular_posicio_fora_pantalla(CLOCK_8_ANGLE);
ship.current_position = ship.initial_position; // Començar fora de pantalla
ship.current_position = ship.initial_position; // Començar fuera de pantalla
// Escales
ship.target_scale = FLOATING_SCALE;
@@ -272,10 +272,10 @@ void ShipAnimator::configurar_nau_p1(TitleShip& ship) {
// Flotació
ship.oscillation_phase = 0.0F;
// Paràmetres d'entrada
// Parámetros de entrada
ship.entry_delay = P1_ENTRY_DELAY;
// Paràmetres d'oscil·lació específics P1
// Parámetros de oscil·lació específics P1
ship.amplitude_x = FLOAT_AMPLITUDE_X;
ship.amplitude_y = FLOAT_AMPLITUDE_Y;
ship.frequency_x = FLOAT_FREQUENCY_X_BASE * P1_FREQUENCY_MULTIPLIER;
@@ -295,9 +295,9 @@ void ShipAnimator::configurar_nau_p2(TitleShip& ship) {
// Posicions (clock 4, bottom-right)
ship.target_position = {.x = P2_TARGET_X(), .y = P2_TARGET_Y()};
// Calcular posició inicial (fora de pantalla)
// Calcular posición inicial (fuera de pantalla)
ship.initial_position = calcular_posicio_fora_pantalla(CLOCK_4_ANGLE);
ship.current_position = ship.initial_position; // Començar fora de pantalla
ship.current_position = ship.initial_position; // Començar fuera de pantalla
// Escales
ship.target_scale = FLOATING_SCALE;
@@ -307,10 +307,10 @@ void ShipAnimator::configurar_nau_p2(TitleShip& ship) {
// Flotació
ship.oscillation_phase = 0.0F;
// Paràmetres d'entrada
// Parámetros de entrada
ship.entry_delay = P2_ENTRY_DELAY;
// Paràmetres d'oscil·lació específics P2
// Parámetros de oscil·lació específics P2
ship.amplitude_x = FLOAT_AMPLITUDE_X;
ship.amplitude_y = FLOAT_AMPLITUDE_Y;
ship.frequency_x = FLOAT_FREQUENCY_X_BASE * P2_FREQUENCY_MULTIPLIER;
@@ -324,7 +324,7 @@ Vec2 ShipAnimator::calcular_posicio_fora_pantalla(float angle_rellotge) const {
using namespace Defaults::Title::Ships;
// Convertir angle del rellotge a radians (per exemple: 240° per clock 8)
// Calcular posició en direcció radial des del centre, però més lluny
// Calcular posición en direcció radial des del centro, pero més lluny
// ENTRY_OFFSET es calcula automàticament: (SHIP_MAX_RADIUS * ENTRY_SCALE_START) + ENTRY_OFFSET_MARGIN
float extended_radius = CLOCK_RADIUS + ENTRY_OFFSET;
+25 -25
View File
@@ -1,5 +1,5 @@
// ship_animator.hpp - Sistema d'animació de naus per a l'escena de títol
// © 2025 Port a C++20 amb SDL3
// ship_animator.hpp - Sistema de animación de naves para l'escena de título
// © 2025 Port a C++20 con SDL3
#pragma once
@@ -13,14 +13,14 @@
namespace Title {
// Estats de l'animació de la ship
// Estats de l'animación de la ship
enum class ShipState {
ENTERING, // Entrant des de fora de pantalla
FLOATING, // Flotant en posició estàtica
EXITING // Volant cap al point de fuga
ENTERING, // Entrant desde fuera de pantalla
FLOATING, // Flotante en posición estàtica
EXITING // Volant hacia el point de fuga
};
// Dades d'una ship individual al títol
// Dades de una ship individual al título
struct TitleShip {
// Identificació
int player_id; // 1 o 2
@@ -30,22 +30,22 @@ struct TitleShip {
float state_time; // Temps acumulat en l'state actual
// Posicions
Vec2 initial_position; // Posició d'inici (fora de pantalla per ENTERING)
Vec2 target_position; // Posició objectiu (rellotge 8 o 4)
Vec2 current_position; // Posició interpolada actual
Vec2 initial_position; // Posición de start (fuera de pantalla per ENTERING)
Vec2 target_position; // Posición objetivo (rellotge 8 o 4)
Vec2 current_position; // Posición interpolada actual
// Escales (simulació eix Z)
float initial_scale; // Escala d'inici (més gran = més a prop)
float target_scale; // Escala objectiu (mida flotació)
float initial_scale; // Escala de start (més grande = més a prop)
float target_scale; // Escala objetivo (mida flotació)
float current_scale; // Escala interpolada actual
// Flotació
float oscillation_phase; // Acumulador de fase per moviment sinusoïdal
float oscillation_phase; // Acumulador de fase per movement sinusoïdal
// Paràmetres d'entrada
float entry_delay; // Delay abans d'entrar (0.0 per P1, 0.5 per P2)
// Parámetros de entrada
float entry_delay; // Delay antes de entrar (0.0 per P1, 0.5 per P2)
// Paràmetres d'oscil·lació per ship
// Parámetros de oscil·lació per ship
float amplitude_x;
float amplitude_y;
float frequency_x;
@@ -58,7 +58,7 @@ struct TitleShip {
bool visible;
};
// Gestor d'animació de naus per a l'escena de títol
// Gestor de animación de naves para l'escena de título
class ShipAnimator {
public:
explicit ShipAnimator(SDL_Renderer* renderer);
@@ -68,27 +68,27 @@ class ShipAnimator {
void update(float delta_time);
void draw() const;
// Control d'state (cridat per TitleScene)
// Control de state (cridat per TitleScene)
void start_entry_animation();
void trigger_exit_animation(); // Anima totes les naus
void trigger_exit_animation_for_player(int player_id); // Anima només una ship (P1=1, P2=2)
void skip_to_floating_state(); // Salta directament a FLOATING sense animació
void trigger_exit_animation(); // Anima todas las naves
void trigger_exit_animation_for_player(int player_id); // Anima solo una ship (P1=1, P2=2)
void skip_to_floating_state(); // Salta directament a FLOATING sin animación
// Control de visibilitat
void set_visible(bool visible);
[[nodiscard]] bool is_animation_complete() const;
[[nodiscard]] bool is_visible() const; // Comprova si alguna ship és visible
[[nodiscard]] bool is_visible() const; // Comprova si alguna ship es visible
private:
SDL_Renderer* renderer_;
std::array<TitleShip, 2> ships_; // Naus P1 i P2
std::array<TitleShip, 2> ships_; // Naves P1 i P2
// Mètodes d'animació
// Métodos de animación
void actualitzar_entering(TitleShip& ship, float delta_time);
void actualitzar_floating(TitleShip& ship, float delta_time);
void actualitzar_exiting(TitleShip& ship, float delta_time);
// Configuració
// Configuración
void configurar_nau_p1(TitleShip& ship);
void configurar_nau_p2(TitleShip& ship);
[[nodiscard]] Vec2 calcular_posicio_fora_pantalla(float angle_rellotge) const;