diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 04b752a..11f5a84 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -12,6 +12,7 @@ #include "core/defaults/audio.hpp" #include "core/defaults/brightness.hpp" #include "core/defaults/controls.hpp" +#include "core/defaults/effects.hpp" #include "core/defaults/enemies.hpp" #include "core/defaults/entities.hpp" #include "core/defaults/floating_score.hpp" diff --git a/source/core/defaults/effects.hpp b/source/core/defaults/effects.hpp new file mode 100644 index 0000000..65d3aeb --- /dev/null +++ b/source/core/defaults/effects.hpp @@ -0,0 +1,39 @@ +// effects.hpp - Constants per a efectes visuals (fireworks, etc.) +// © 2026 JailDesigner + +#pragma once + +namespace Defaults::FX::Firework { + + // Velocitat inicial radial al spawn (px/s) i variació entre punts. + constexpr float SPEED = 140.0F; + constexpr float SPEED_VARIATION = 30.0F; // ± + + // Quantitat de línies per burst (per defecte). + constexpr int N_POINTS = 8; + + // Distribució angular: jitter aleatori sobre el repartiment uniforme. + constexpr float ANGULAR_JITTER_DEG = 12.0F; + + // Fase 1 (creixement): la línia neix amb longitud 0 i creix fins a max. + constexpr float GROW_DURATION = 0.08F; // s + constexpr float MAX_LENGTH = 14.0F; // px + + // Fricció lineal (px/s²). Negativa per frenar. + constexpr float FRICTION = -180.0F; + + // Llindar de mort: per sota d'aquesta longitud (px) o brillor, la + // partícula es marca inactiva. + constexpr float MIN_LENGTH = 0.5F; + constexpr float MIN_BRIGHTNESS = 0.02F; + + // Brillor inicial per defecte. + constexpr float INITIAL_BRIGHTNESS = 1.0F; + + // Restitució en rebot contra els límits del PLAYAREA (mateix patró que debris). + constexpr float RESTITUTION_BOUNDS = 0.7F; + + // Mida del pool. 8 punts × ~25 bursts simultanis. + constexpr int POOL_SIZE = 200; + +} // namespace Defaults::FX::Firework diff --git a/source/game/effects/firework.hpp b/source/game/effects/firework.hpp new file mode 100644 index 0000000..ed80648 --- /dev/null +++ b/source/game/effects/firework.hpp @@ -0,0 +1,41 @@ +// firework.hpp - Partícula d'efecte starburst (una línia per partícula) +// © 2026 JailDesigner + +#pragma once +#include + +#include "core/types.hpp" + +namespace Effects { + + // Una partícula de firework: una línia que neix d'un punt origen, viatja + // radialment cap a fora i s'encongeix conforme perd velocitat. + // + // Geometria (es deriva al draw): + // head: extrem davanter (es mou amb velocity). + // tail = head − velocity_normalitzada × current_length. + // + // Cicle de vida: + // Fase 1 (temps_vida < grow_duration): current_length creix linealment + // de 0 a max_length. Brillor al màxim. + // Fase 2: current_length = max_length × (speed/initial_speed) i brillor + // amb la mateixa proporció. Mor quan length o brightness cauen sota + // llindar. + struct Firework { + Vec2 head; // Punta davantera (posició actual) + Vec2 velocity; // Velocidad en px/s + float acceleration; // Fricció lineal (px/s², negativa) + + float current_length; // Longitud actual del segment (px) + float max_length; // Longitud màxima (final de la fase de creixement) + float grow_duration; // Temps de creixement de 0 a max_length (s) + + float temps_vida; // Acumulador (s) + float initial_speed; // Speed inicial per a la proporció de fase 2 + + float brightness; // 0..1 + SDL_Color color{}; // alpha==0 → oscilador global + bool active; + }; + +} // namespace Effects diff --git a/source/game/effects/firework_manager.cpp b/source/game/effects/firework_manager.cpp new file mode 100644 index 0000000..1e54aad --- /dev/null +++ b/source/game/effects/firework_manager.cpp @@ -0,0 +1,219 @@ +// firework_manager.cpp - Implementació del gestor de fireworks +// © 2026 JailDesigner + +#include "firework_manager.hpp" + +#include +#include +#include + +#include "core/defaults.hpp" +#include "core/rendering/line_renderer.hpp" + +namespace Effects { + + namespace { + // Random float in [-1, 1]. + auto randSigned() -> float { + return ((std::rand() / static_cast(RAND_MAX)) * 2.0F) - 1.0F; + } + + // Rebot del head contra els límits del PLAYAREA (mateix patró que + // DebrisManager::bounceOffPlayArea). + void bounceOffPlayArea(Vec2& head, Vec2& velocity) { + const auto& bounds = Defaults::Zones::PLAYAREA; + constexpr float REST = Defaults::FX::Firework::RESTITUTION_BOUNDS; + const float MIN_X = bounds.x; + const float MAX_X = bounds.x + bounds.w; + const float MIN_Y = bounds.y; + const float MAX_Y = bounds.y + bounds.h; + if (head.x < MIN_X) { + head.x = MIN_X; + if (velocity.x < 0.0F) { + velocity.x = -velocity.x * REST; + } + } + if (head.x > MAX_X) { + head.x = MAX_X; + if (velocity.x > 0.0F) { + velocity.x = -velocity.x * REST; + } + } + if (head.y < MIN_Y) { + head.y = MIN_Y; + if (velocity.y < 0.0F) { + velocity.y = -velocity.y * REST; + } + } + if (head.y > MAX_Y) { + head.y = MAX_Y; + if (velocity.y > 0.0F) { + velocity.y = -velocity.y * REST; + } + } + } + } // namespace + + FireworkManager::FireworkManager(Rendering::Renderer* renderer) + : renderer_(renderer) { + for (auto& fw : pool_) { + fw.active = false; + } + } + + void FireworkManager::spawn(const Vec2& origen, + SDL_Color color, + float initial_speed, + int n_points, + float initial_brightness) { + if (n_points <= 0) { + return; + } + + const float ANGLE_STEP = 2.0F * Defaults::Math::PI / static_cast(n_points); + const float JITTER_RAD = + Defaults::FX::Firework::ANGULAR_JITTER_DEG * Defaults::Math::PI / 180.0F; + + for (int i = 0; i < n_points; i++) { + Firework* fw = findFreeSlot(); + if (fw == nullptr) { + return; // Pool ple + } + + const float BASE_ANGLE = ANGLE_STEP * static_cast(i); + const float JITTER = randSigned() * JITTER_RAD; + const float ANGLE = BASE_ANGLE + JITTER; + + const float SPEED = + initial_speed + (randSigned() * Defaults::FX::Firework::SPEED_VARIATION); + + fw->head = origen; + fw->velocity = {.x = std::cos(ANGLE) * SPEED, .y = std::sin(ANGLE) * SPEED}; + fw->acceleration = Defaults::FX::Firework::FRICTION; + + fw->current_length = 0.0F; + fw->max_length = Defaults::FX::Firework::MAX_LENGTH; + fw->grow_duration = Defaults::FX::Firework::GROW_DURATION; + + fw->temps_vida = 0.0F; + fw->initial_speed = SPEED; + + fw->brightness = initial_brightness; + fw->color = color; + fw->active = true; + } + } + + void FireworkManager::update(float delta_time) { + for (auto& fw : pool_) { + if (!fw.active) { + continue; + } + + fw.temps_vida += delta_time; + + // 1. Fricció lineal (aplicar en la direcció del movement). + const float SPEED = std::sqrt( + (fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y)); + + if (SPEED > 1.0F) { + const float DIR_X = fw.velocity.x / SPEED; + const float DIR_Y = fw.velocity.y / SPEED; + float new_speed = SPEED + (fw.acceleration * delta_time); + new_speed = std::max(new_speed, 0.0F); + fw.velocity.x = DIR_X * new_speed; + fw.velocity.y = DIR_Y * new_speed; + } else { + fw.velocity = {.x = 0.0F, .y = 0.0F}; + } + + // 2. Avançar head. + fw.head.x += fw.velocity.x * delta_time; + fw.head.y += fw.velocity.y * delta_time; + + // 3. Rebot contra PLAYAREA. + bounceOffPlayArea(fw.head, fw.velocity); + + // 4. Calcular longitud i brillor segons fase. + if (fw.temps_vida < fw.grow_duration) { + // Fase 1: creixement lineal de 0 a max_length. + const float T = fw.temps_vida / fw.grow_duration; + fw.current_length = fw.max_length * T; + fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS; + } else { + // Fase 2: longitud i brillor proporcionals a la velocity actual. + const float CURRENT_SPEED = std::sqrt( + (fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y)); + const float RATIO = (fw.initial_speed > 0.01F) + ? std::min(CURRENT_SPEED / fw.initial_speed, 1.0F) + : 0.0F; + fw.current_length = fw.max_length * RATIO; + fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS * RATIO; + } + + // 5. Morir si la longitud o el brillor cauen sota llindar. + if (fw.current_length < Defaults::FX::Firework::MIN_LENGTH || + fw.brightness < Defaults::FX::Firework::MIN_BRIGHTNESS) { + fw.active = false; + } + } + } + + void FireworkManager::draw() const { + for (const auto& fw : pool_) { + if (!fw.active) { + continue; + } + + // tail = head − velocity_normalitzada × current_length. + const float SPEED = std::sqrt( + (fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y)); + if (SPEED < 0.01F) { + continue; // Sense direcció no podem orientar la línia. + } + + const float DIR_X = fw.velocity.x / SPEED; + const float DIR_Y = fw.velocity.y / SPEED; + + const Vec2 TAIL = { + .x = fw.head.x - (DIR_X * fw.current_length), + .y = fw.head.y - (DIR_Y * fw.current_length), + }; + + Rendering::linea(renderer_, + static_cast(fw.head.x), + static_cast(fw.head.y), + static_cast(TAIL.x), + static_cast(TAIL.y), + fw.brightness, + 0.0F, + fw.color); + } + } + + void FireworkManager::reset() { + for (auto& fw : pool_) { + fw.active = false; + } + } + + auto FireworkManager::getActiveCount() const -> int { + int count = 0; + for (const auto& fw : pool_) { + if (fw.active) { + count++; + } + } + return count; + } + + auto FireworkManager::findFreeSlot() -> Firework* { + for (auto& fw : pool_) { + if (!fw.active) { + return &fw; + } + } + return nullptr; + } + +} // namespace Effects diff --git a/source/game/effects/firework_manager.hpp b/source/game/effects/firework_manager.hpp new file mode 100644 index 0000000..864470e --- /dev/null +++ b/source/game/effects/firework_manager.hpp @@ -0,0 +1,50 @@ +// firework_manager.hpp - Gestor de bursts radials (fireworks) +// © 2026 JailDesigner + +#pragma once + +#include + +#include + +#include "core/defaults.hpp" +#include "core/rendering/render_context.hpp" +#include "core/types.hpp" +#include "firework.hpp" + +namespace Effects { + + // Pool de partícules. spawn() emet un burst d'N línies radials des + // d'`origen`. Cada partícula viu independent (update/draw/rebot). + class FireworkManager { + public: + explicit FireworkManager(Rendering::Renderer* renderer); + + // Emet un burst radial: + // origen: punt central del burst. + // color: color de les línies (heretat del pare). + // initial_speed: velocitat radial inicial (px/s). + // n_points: nombre de línies. Default Defaults::FX::Firework::N_POINTS. + // initial_brightness: 0..1. + void spawn(const Vec2& origen, + SDL_Color color, + float initial_speed = Defaults::FX::Firework::SPEED, + int n_points = Defaults::FX::Firework::N_POINTS, + float initial_brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS); + + void update(float delta_time); + void draw() const; + void reset(); + + [[nodiscard]] auto getActiveCount() const -> int; + + private: + Rendering::Renderer* renderer_; + + static constexpr int POOL_SIZE = Defaults::FX::Firework::POOL_SIZE; + std::array pool_; + + auto findFreeSlot() -> Firework*; + }; + +} // namespace Effects diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index f7cc050..71862f2 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -27,6 +27,7 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) : sdl_(sdl), context_(context), debris_manager_(sdl.getRenderer()), + firework_manager_(sdl.getRenderer()), floating_score_manager_(sdl.getRenderer()), text_(sdl.getRenderer()) { // Recuperar configuración de match des del context @@ -253,6 +254,7 @@ auto GameScene::stepContinueScreen(float delta_time) -> bool { bullet.update(delta_time); } debris_manager_.update(delta_time); + firework_manager_.update(delta_time); floating_score_manager_.update(delta_time); return true; } @@ -277,6 +279,7 @@ auto GameScene::stepGameOver(float delta_time) -> bool { bullet.update(delta_time); } debris_manager_.update(delta_time); + firework_manager_.update(delta_time); floating_score_manager_.update(delta_time); return true; } @@ -324,6 +327,7 @@ void GameScene::stepDeathSequence(float delta_time) { bullet.update(delta_time); } debris_manager_.update(delta_time); + firework_manager_.update(delta_time); floating_score_manager_.update(delta_time); } // El bool 'algun_mort' es puramente interno: no aporta nada al caller @@ -392,6 +396,7 @@ void GameScene::runStageLevelStart(float delta_time) { bullet.update(delta_time); } debris_manager_.update(delta_time); + firework_manager_.update(delta_time); } void GameScene::runStagePlaying(float delta_time) { @@ -423,6 +428,7 @@ void GameScene::runStagePlaying(float delta_time) { runCollisionDetections(); debris_manager_.update(delta_time); + firework_manager_.update(delta_time); floating_score_manager_.update(delta_time); } @@ -439,6 +445,7 @@ void GameScene::runStageLevelCompleted(float delta_time) { bullet.update(delta_time); } debris_manager_.update(delta_time); + firework_manager_.update(delta_time); floating_score_manager_.update(delta_time); } @@ -511,6 +518,7 @@ void GameScene::drawContinueState() { drawEnemies(); drawBullets(); debris_manager_.draw(); + firework_manager_.draw(); floating_score_manager_.draw(); drawScoreboard(); drawContinue(); @@ -521,6 +529,7 @@ void GameScene::drawGameOverState() { drawEnemies(); drawBullets(); debris_manager_.draw(); + firework_manager_.draw(); floating_score_manager_.draw(); const std::string GAME_OVER_TEXT = "GAME OVER"; @@ -587,6 +596,7 @@ void GameScene::drawLevelStartState() { drawActiveShipsAlive(); drawBullets(); debris_manager_.draw(); + firework_manager_.draw(); floating_score_manager_.draw(); drawStageMessage(stage_manager_->getLevelStartMessage()); drawScoreboard(); @@ -598,6 +608,7 @@ void GameScene::drawPlayingState() { drawEnemies(); drawBullets(); debris_manager_.draw(); + firework_manager_.draw(); floating_score_manager_.draw(); drawScoreboard(); } @@ -607,6 +618,7 @@ void GameScene::drawLevelCompletedState() { drawActiveShipsAlive(); drawBullets(); debris_manager_.draw(); + firework_manager_.draw(); floating_score_manager_.draw(); drawStageMessage(StageSystem::Constants::MISSATGE_LEVEL_COMPLETED); drawScoreboard(); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 32d9e2a..320c785 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -17,6 +17,7 @@ #include "core/types.hpp" #include "game/constants.hpp" #include "game/effects/debris_manager.hpp" +#include "game/effects/firework_manager.hpp" #include "game/effects/floating_score_manager.hpp" #include "game/entities/bullet.hpp" #include "game/entities/enemy.hpp" @@ -53,6 +54,7 @@ class GameScene final : public Scene { // Efectes visuals Effects::DebrisManager debris_manager_; + Effects::FireworkManager firework_manager_; Effects::FloatingScoreManager floating_score_manager_; // Estat del juego