diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fcf3be..b17086a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ set(APP_SOURCES source/game/entities/nau.cpp source/game/entities/bala.cpp source/game/entities/enemic.cpp + source/game/effects/debris_manager.cpp ) # Configuración de SDL3 diff --git a/Makefile b/Makefile index e3f2c9e..32bcd81 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,8 @@ APP_SOURCES := \ source/game/escenes/escena_joc.cpp \ source/game/entities/nau.cpp \ source/game/entities/bala.cpp \ - source/game/entities/enemic.cpp + source/game/entities/enemic.cpp \ + source/game/effects/debris_manager.cpp # ============================================================================== # INCLUDES diff --git a/source/game/effects/debris.hpp b/source/game/effects/debris.hpp new file mode 100644 index 0000000..99ab204 --- /dev/null +++ b/source/game/effects/debris.hpp @@ -0,0 +1,33 @@ +// debris.hpp - Fragment de línia volant (explosió de formes) +// © 2025 Port a C++20 amb 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 forma destruïda (nau, enemic, bala) +struct Debris { + // Geometria del segment (2 punts en coordenades mundials) + Punt p1; // Punt inicial del segment + Punt p2; // Punt final del segment + + // Física + Punt velocitat; // Velocitat en px/s (components x, y) + float acceleracio; // Acceleració negativa (fricció) en px/s² + + // Rotació + float angle_rotacio; // Angle de rotació acumulat (radians) + float velocitat_rot; // Velocitat de rotació en rad/s + + // Estat de vida + float temps_vida; // Temps transcorregut (segons) + float temps_max; // Temps de vida màxim (segons) + bool actiu; // Està actiu? + + // Shrinking (reducció de distància entre punts) + float factor_shrink; // Factor de reducció per segon (0.0-1.0) +}; + +} // namespace Effects diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp new file mode 100644 index 0000000..a61e0de --- /dev/null +++ b/source/game/effects/debris_manager.cpp @@ -0,0 +1,275 @@ +// debris_manager.cpp - Implementació del gestor de fragments +// © 2025 Port a C++20 amb SDL3 + +#include "debris_manager.hpp" +#include "core/defaults.hpp" +#include "core/rendering/line_renderer.hpp" +#include +#include +#include + +namespace Effects { + +// Helper: transformar punt amb rotació, escala i trasllació +// (Copiat de shape_renderer.cpp:12-34) +static Punt transform_point(const Punt &point, const Punt &shape_centre, + const Punt &posicio, float angle, float escala) { + // 1. Centrar el punt respecte al centre de la forma + float centered_x = point.x - shape_centre.x; + float centered_y = point.y - shape_centre.y; + + // 2. Aplicar escala al punt centrat + float scaled_x = centered_x * escala; + float scaled_y = centered_y * escala; + + // 3. Aplicar rotació + 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 + return {rotated_x + posicio.x, rotated_y + posicio.y}; +} + +DebrisManager::DebrisManager(SDL_Renderer *renderer) : renderer_(renderer) { + // Inicialitzar tots els debris com inactius + for (auto &debris : debris_pool_) { + debris.actiu = false; + } +} + +void DebrisManager::explotar(const std::shared_ptr &shape, + const Punt ¢re, float angle, float escala, + float velocitat_base) { + if (!shape || !shape->es_valida()) { + return; + } + + // Obtenir centre de la forma per a transformacions + const Punt &shape_centre = shape->get_centre(); + + // Iterar sobre totes les primitives de la forma + for (const auto &primitive : shape->get_primitives()) { + // Processar cada segment de línia + std::vector> segments; + + if (primitive.type == Graphics::PrimitiveType::POLYLINE) { + // Polyline: extreure segments consecutius + for (size_t i = 0; i < primitive.points.size() - 1; i++) { + segments.push_back({primitive.points[i], primitive.points[i + 1]}); + } + } else { // PrimitiveType::LINE + // Line: un únic segment + if (primitive.points.size() >= 2) { + segments.push_back({primitive.points[0], primitive.points[1]}); + } + } + + // Crear debris per a cada segment + for (const auto &[local_p1, local_p2] : segments) { + // 1. Transformar punts locals → coordenades mundials + Punt world_p1 = + transform_point(local_p1, shape_centre, centre, angle, escala); + Punt world_p2 = + transform_point(local_p2, shape_centre, centre, angle, escala); + + // 2. Trobar slot lliure + Debris *debris = trobar_slot_lliure(); + if (!debris) { + std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; + return; // Pool ple + } + + // 3. Inicialitzar geometria + debris->p1 = world_p1; + debris->p2 = world_p2; + + // 4. Calcular direcció perpendicular + Punt direccio = calcular_direccio_perpendicular(world_p1, world_p2); + + // 5. Velocitat inicial (base ± variació aleatòria) + float speed = + velocitat_base + + ((std::rand() / static_cast(RAND_MAX)) * 2.0f - 1.0f) * + Defaults::Physics::Debris::VARIACIO_VELOCITAT; + + debris->velocitat.x = direccio.x * speed; + debris->velocitat.y = direccio.y * speed; + debris->acceleracio = Defaults::Physics::Debris::ACCELERACIO; + + // 6. Rotació lenta aleatòria + debris->velocitat_rot = + Defaults::Physics::Debris::ROTACIO_MIN + + (std::rand() / static_cast(RAND_MAX)) * + (Defaults::Physics::Debris::ROTACIO_MAX - + Defaults::Physics::Debris::ROTACIO_MIN); + + // 50% probabilitat de rotació en sentit contrari + if (std::rand() % 2 == 0) { + debris->velocitat_rot = -debris->velocitat_rot; + } + + debris->angle_rotacio = 0.0f; + + // 7. Configurar vida i shrinking + debris->temps_vida = 0.0f; + debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA; + debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; + + // 8. Activar + debris->actiu = true; + } + } +} + +void DebrisManager::actualitzar(float delta_time) { + for (auto &debris : debris_pool_) { + if (!debris.actiu) + continue; + + // 1. Actualitzar temps de vida + debris.temps_vida += delta_time; + + // Desactivar si ha superat temps màxim + if (debris.temps_vida >= debris.temps_max) { + debris.actiu = false; + continue; + } + + // 2. Actualitzar velocitat (desacceleració) + // Aplicar fricció en la direcció del moviment + float speed = std::sqrt(debris.velocitat.x * debris.velocitat.x + + debris.velocitat.y * debris.velocitat.y); + + if (speed > 1.0f) { + // Calcular direcció normalitzada + float dir_x = debris.velocitat.x / speed; + float dir_y = debris.velocitat.y / speed; + + // Aplicar acceleració negativa (fricció) + float nova_speed = speed + debris.acceleracio * delta_time; + if (nova_speed < 0.0f) + nova_speed = 0.0f; + + debris.velocitat.x = dir_x * nova_speed; + debris.velocitat.y = dir_y * nova_speed; + } else { + // Velocitat molt baixa, aturar + debris.velocitat.x = 0.0f; + debris.velocitat.y = 0.0f; + } + + // 3. Calcular centre del segment + Punt centre = {(debris.p1.x + debris.p2.x) / 2.0f, + (debris.p1.y + debris.p2.y) / 2.0f}; + + // 4. Actualitzar posició del centre + centre.x += debris.velocitat.x * delta_time; + centre.y += debris.velocitat.y * delta_time; + + // 5. Actualitzar rotació + debris.angle_rotacio += debris.velocitat_rot * delta_time; + + // 6. Aplicar shrinking (reducció de distància entre punts) + 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 punts + float dx = debris.p2.x - debris.p1.x; + float dy = debris.p2.y - debris.p1.y; + + // 7. Reconstruir segment amb nova mida i rotació + 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); + } +} + +void DebrisManager::dibuixar() const { + for (const auto &debris : debris_pool_) { + if (!debris.actiu) + continue; + + // Dibuixar segment de línia + Rendering::linea(renderer_, static_cast(debris.p1.x), + static_cast(debris.p1.y), + static_cast(debris.p2.x), + static_cast(debris.p2.y), true); + } +} + +Debris *DebrisManager::trobar_slot_lliure() { + for (auto &debris : debris_pool_) { + if (!debris.actiu) { + return &debris; + } + } + return nullptr; // Pool ple +} + +Punt DebrisManager::calcular_direccio_perpendicular(const Punt &p1, + const Punt &p2) const { + // 1. Calcular vector de la línia (p1 → p2) + float dx = p2.x - p1.x; + float dy = p2.y - p1.y; + + // 2. Normalitzar (obtenir vector unitari) + float length = std::sqrt(dx * dx + dy * dy); + if (length < 0.001f) { + // Línia degenerada, retornar direcció aleatòria + float angle_rand = + (std::rand() / static_cast(RAND_MAX)) * 2.0f * Defaults::Math::PI; + return {std::cos(angle_rand), std::sin(angle_rand)}; + } + + dx /= length; + dy /= length; + + // 3. Rotar 90° (perpendicular) + // Rotació 90° sentit antihorari: (x,y) → (-y, x) + float perp_x = -dy; + float perp_y = dx; + + // 4. Afegir variació aleatòria petita (±15°) + float angle_variacio = + ((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0f; + + float cos_v = std::cos(angle_variacio); + float sin_v = std::sin(angle_variacio); + + float final_x = perp_x * cos_v - perp_y * sin_v; + float final_y = perp_x * sin_v + perp_y * cos_v; + + // 5. Afegir ± direcció aleatòria (50% probabilitat d'invertir) + if (std::rand() % 2 == 0) { + final_x = -final_x; + final_y = -final_y; + } + + return {final_x, final_y}; +} + +void DebrisManager::reiniciar() { + for (auto &debris : debris_pool_) { + debris.actiu = false; + } +} + +int DebrisManager::get_num_actius() const { + int count = 0; + for (const auto &debris : debris_pool_) { + if (debris.actiu) + count++; + } + return count; +} + +} // namespace Effects diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp new file mode 100644 index 0000000..fc91357 --- /dev/null +++ b/source/game/effects/debris_manager.hpp @@ -0,0 +1,59 @@ +// debris_manager.hpp - Gestor de fragments d'explosions +// © 2025 Port a C++20 amb SDL3 + +#pragma once +#include "debris.hpp" +#include "core/graphics/shape.hpp" +#include "core/types.hpp" +#include +#include +#include + +namespace Effects { + +// Gestor de fragments d'explosions +// Manté un pool d'objectes Debris i gestiona el seu cicle de vida +class DebrisManager { +public: + explicit DebrisManager(SDL_Renderer *renderer); + + // Crear explosió a partir d'una forma + // - shape: forma vectorial a explotar + // - centre: posició del centre de l'objecte + // - angle: orientació de l'objecte (radians) + // - escala: escala de l'objecte (1.0 = normal) + // - velocitat_base: velocitat inicial dels fragments (px/s) + void explotar(const std::shared_ptr &shape, + const Punt ¢re, float angle, float escala, + float velocitat_base); + + // Actualitzar tots els fragments actius + void actualitzar(float delta_time); + + // Dibuixar tots els fragments actius + void dibuixar() const; + + // Reiniciar tots els fragments (neteja) + void reiniciar(); + + // Obtenir número de fragments actius + int get_num_actius() const; + +private: + SDL_Renderer *renderer_; + + // Pool de fragments (màxim concurrent) + // Un pentàgon té 5 línies, 15 enemics = 75 línies + // + nau (3 línies) + bales (5 línies * 3) = 93 línies màxim + // Arrodonit a 100 per seguretat + static constexpr int MAX_DEBRIS = 100; + std::array debris_pool_; + + // Trobar primer slot inactiu + Debris *trobar_slot_lliure(); + + // Calcular direcció perpendicular a un segment + Punt calcular_direccio_perpendicular(const Punt &p1, const Punt &p2) const; +}; + +} // namespace Effects diff --git a/source/game/entities/enemic.hpp b/source/game/entities/enemic.hpp index ec90d4e..c1d8316 100644 --- a/source/game/entities/enemic.hpp +++ b/source/game/entities/enemic.hpp @@ -20,6 +20,7 @@ public: // Getters (API pública sense canvis) bool esta_actiu() const { return esta_; } const Punt &get_centre() const { return centre_; } + const std::shared_ptr &get_forma() const { return forma_; } void destruir() { esta_ = false; } private: diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 85c6185..cc55206 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -10,10 +10,11 @@ #include #include #include +#include EscenaJoc::EscenaJoc(SDLManager &sdl) - : sdl_(sdl), nau_(sdl.obte_renderer()), itocado_(0), - text_(sdl.obte_renderer()) { + : sdl_(sdl), debris_manager_(sdl.obte_renderer()), + nau_(sdl.obte_renderer()), itocado_(0), text_(sdl.obte_renderer()) { // Inicialitzar bales amb renderer for (auto &bala : bales_) { bala = Bala(sdl.obte_renderer()); @@ -116,6 +117,9 @@ void EscenaJoc::actualitzar(float delta_time) { for (auto &bala : bales_) { bala.actualitzar(delta_time); } + + // Actualitzar fragments d'explosions + debris_manager_.actualitzar(delta_time); } void EscenaJoc::dibuixar() { @@ -135,6 +139,9 @@ void EscenaJoc::dibuixar() { bala.dibuixar(); } + // Dibuixar fragments d'explosions (després d'altres objectes) + debris_manager_.dibuixar(); + // Dibuixar marcador dibuixar_marcador(); } @@ -146,19 +153,36 @@ void EscenaJoc::processar_input(const SDL_Event &event) { if (event.type == SDL_EVENT_KEY_DOWN) { switch (event.key.key) { - case SDLK_SPACE: - // Disparar (Fase 9) - // Basat en el codi Pascal original: crear bala en posició de la nau - // El joc original només permetia 1 bala activa alhora + case SDLK_SPACE: { + // TEMPORAL: Explotar enemic aleatori (per testing) + // TODO: Restaurar dispars quan el sistema d'explosions estiga validat - // Buscar primera bala inactiva - for (auto &bala : bales_) { - if (!bala.esta_activa()) { - bala.disparar(nau_.get_centre(), nau_.get_angle()); - break; + // Buscar tots els enemics actius + std::vector enemics_actius; + for (int i = 0; i < Constants::MAX_ORNIS; i++) { + if (orni_[i].esta_actiu()) { + enemics_actius.push_back(i); } } + + if (!enemics_actius.empty()) { + // Seleccionar enemic aleatori + int idx = enemics_actius[std::rand() % enemics_actius.size()]; + + // Crear explosió + debris_manager_.explotar(orni_[idx].get_forma(), + orni_[idx].get_centre(), + 0.0f, // angle (enemics no roten físicament) + 1.0f, // escala + Defaults::Physics::Debris::VELOCITAT_BASE); + + // Desactivar enemic + orni_[idx].destruir(); + + std::cout << "[TEST] Enemic " << idx << " explotat!\n"; + } break; + } default: break; diff --git a/source/game/escenes/escena_joc.hpp b/source/game/escenes/escena_joc.hpp index aa835dc..fe20920 100644 --- a/source/game/escenes/escena_joc.hpp +++ b/source/game/escenes/escena_joc.hpp @@ -7,6 +7,7 @@ #include "../../core/graphics/vector_text.hpp" #include "../../core/rendering/sdl_manager.hpp" +#include "../effects/debris_manager.hpp" #include "../../core/types.hpp" #include "../constants.hpp" #include "../entities/bala.hpp" @@ -31,6 +32,9 @@ public: private: SDLManager &sdl_; + // Efectes visuals + Effects::DebrisManager debris_manager_; + // Estat del joc Nau nau_; std::array orni_;