diff --git a/data/shapes/title_flash.shp b/data/shapes/title_flash.shp new file mode 100644 index 0000000..cdcce3e --- /dev/null +++ b/data/shapes/title_flash.shp @@ -0,0 +1,9 @@ +# title_flash.shp - Sparkle 4-puntes amb costats còncaus (Atari-style) +# 4 puntes als cardinals (radi 30) i valls còncaus als 45° (corba Bezier +# quadràtica amb control point ±8). 5 punts per arc subdividint la corba. + +name: title_flash +scale: 1.0 +center: 0, 0 + +polyline: 0,-30 3.76,-21.76 8.64,-14.64 14.64,-8.64 21.76,-3.76 30,0 21.76,3.76 14.64,8.64 8.64,14.64 3.76,21.76 0,30 -3.76,21.76 -8.64,14.64 -14.64,8.64 -21.76,3.76 -30,0 -21.76,-3.76 -14.64,-8.64 -8.64,-14.64 -3.76,-21.76 0,-30 diff --git a/source/game/scenes/title_scene.cpp b/source/game/scenes/title_scene.cpp index 4d0fd03..7012384 100644 --- a/source/game/scenes/title_scene.cpp +++ b/source/game/scenes/title_scene.cpp @@ -73,6 +73,15 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context) // correcte de la intro coreografiada (també quan venim de JUMP_TO_TITLE_MAIN). ship_animator_->setVisible(false); + // Flash que tapa el "pop" final de la nau al VP. Es spawneja al centre + // de pantalla (= projecció del VP) quan ship_animator avisa. + flash_shape_ = Graphics::ShapeLoader::load("title_flash.shp"); + ship_animator_->setOnShipDisappear([this](int /*player_id*/) { + triggerFlash(Vec2{ + .x = static_cast(Defaults::Window::WIDTH) / 2.0F, + .y = static_cast(Defaults::Window::HEIGHT) / 2.0F}); + }); + initTitle(); inicialitzarJailgames(); @@ -294,6 +303,7 @@ void TitleScene::update(float delta_time) { estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) { ship_animator_->update(delta_time); } + updateFlashes(delta_time); switch (estat_actual_) { case TitleState::STARFIELD_FADE_IN: @@ -524,6 +534,7 @@ void TitleScene::draw() { estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) { ship_animator_->draw(); } + drawFlashes(); if (estat_actual_ == TitleState::STARFIELD_FADE_IN || estat_actual_ == TitleState::STARFIELD) { return; @@ -629,3 +640,47 @@ auto TitleScene::checkStartGameButtonPressed() -> bool { void TitleScene::handleEvent(const SDL_Event& event) { (void)event; } + +namespace { + constexpr float FLASH_DURATION = 0.40F; + constexpr float FLASH_MAX_SCALE = 2.5F; + constexpr SDL_Color FLASH_COLOR = {.r = 255, .g = 255, .b = 255, .a = 255}; +} // namespace + +void TitleScene::triggerFlash(Vec2 pos) { + for (auto& f : flashes_) { + if (!f.active) { + f.active = true; + f.position = pos; + f.timer = 0.0F; + return; + } + } +} + +void TitleScene::updateFlashes(float delta_time) { + for (auto& f : flashes_) { + if (!f.active) { + continue; + } + f.timer += delta_time; + if (f.timer >= FLASH_DURATION) { + f.active = false; + } + } +} + +void TitleScene::drawFlashes() { + if (!flash_shape_) { + return; + } + for (const auto& f : flashes_) { + if (!f.active) { + continue; + } + // Escala 0 → max al midpoint → 0. Sinus simètric. + const float T_NORM = f.timer / FLASH_DURATION; + const float SCALE = FLASH_MAX_SCALE * std::sin(T_NORM * Defaults::Math::PI); + Rendering::renderShape(sdl_.getRenderer(), flash_shape_, f.position, 0.0F, SCALE, 1.0F, 1.0F, FLASH_COLOR); + } +} diff --git a/source/game/scenes/title_scene.hpp b/source/game/scenes/title_scene.hpp index 1191f46..6f03692 100644 --- a/source/game/scenes/title_scene.hpp +++ b/source/game/scenes/title_scene.hpp @@ -63,6 +63,20 @@ class TitleScene final : public Scene { std::unique_ptr camera_; std::unique_ptr starfield_; std::unique_ptr ship_animator_; + + // Destell que tapa el "pop" final de cada nau quan arriba al VP. + // Pool fix de 2 (una per nau). Anima escala 0→max→0. + struct Flash { + Vec2 position{}; + float timer{0.0F}; + bool active{false}; + }; + std::array flashes_{}; + std::shared_ptr flash_shape_; + + void triggerFlash(Vec2 pos); + void updateFlashes(float delta_time); + void drawFlashes(); TitleState estat_actual_{TitleState::STARFIELD_FADE_IN}; float temps_acumulat_{0.0F}; diff --git a/source/game/title/ship_animator.cpp b/source/game/title/ship_animator.cpp index a81acca..8400d48 100644 --- a/source/game/title/ship_animator.cpp +++ b/source/game/title/ship_animator.cpp @@ -142,9 +142,14 @@ namespace Title { case ShipState::FLOATING: updateFloating(ship, delta_time); break; - case ShipState::EXITING: + case ShipState::EXITING: { updateExiting(ship, delta_time); + // Transició a invisible: la nau acaba d'arribar al VP. + if (!ship.visible && on_ship_disappear_) { + on_ship_disappear_(ship.player_id); + } break; + } } } } diff --git a/source/game/title/ship_animator.hpp b/source/game/title/ship_animator.hpp index 24cc7fd..904e6f3 100644 --- a/source/game/title/ship_animator.hpp +++ b/source/game/title/ship_animator.hpp @@ -10,6 +10,7 @@ #include #include +#include #include "core/graphics/camera3d.hpp" #include "core/graphics/wireframe3d.hpp" @@ -70,10 +71,16 @@ namespace Title { [[nodiscard]] auto isAnimationComplete() const -> bool; [[nodiscard]] auto isVisible() const -> bool; + // Callback disparat quan una nau acaba l'EXITING (es torna invisible + // al VP). Útil per a un destell que tapi el pop final. + using ShipDisappearCallback = std::function; + void setOnShipDisappear(ShipDisappearCallback cb) { on_ship_disappear_ = std::move(cb); } + private: Rendering::Renderer* renderer_; const Graphics::Camera3D* camera_; std::array ships_; + ShipDisappearCallback on_ship_disappear_; static void updateEntering(TitleShip& ship, float delta_time); static void updateFloating(TitleShip& ship, float delta_time);