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:
1 parent
7ee359b910
commit
bf83f161b0
71 files changed
+1142
-1148
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user