// title_scene.hpp - Escena de títol en 3D real // © 2026 JailDesigner // // Clon de `TitleScene` (2D) que substitueix `Graphics::Starfield` per // `Graphics::Starfield` i `Title::ShipAnimator` per `Title::ShipAnimator`, // afegint una `Graphics::Camera3D` que projecta l'escena en perspectiva real. // Tot el bloc d'overlay 2D (logo "ORNI ATTACK!", "PRESS START TO PLAY", peu // "JAILGAMES + copyright") es manté idèntic. // #pragma once #include #include #include #include #include #include "core/graphics/camera3d.hpp" #include "core/graphics/curtain.hpp" #include "core/graphics/shape.hpp" #include "core/graphics/starfield.hpp" #include "core/graphics/vector_text.hpp" #include "core/input/input_types.hpp" #include "core/rendering/sdl_manager.hpp" #include "core/system/game_config.hpp" #include "core/system/scene.hpp" #include "core/system/scene_context.hpp" #include "core/types.hpp" #include "game/title/ship_animator.hpp" class TitleScene final : public Scene { public: explicit TitleScene(SDLManager& sdl, SceneManager::SceneContext& context); ~TitleScene() override; void handleEvent(const SDL_Event& event) override; void update(float delta_time) override; void draw() override; [[nodiscard]] auto isFinished() const -> bool override; private: enum class TitleState : std::uint8_t { STARFIELD_FADE_IN, STARFIELD, MAIN, PLAYER_JOIN_PHASE, BLACK_SCREEN, DEMO_DIVE, // Attract: dive de càmera cap al punt de fuga DEMO_CURTAIN, // Attract: cortinilla negra que cau i tapa abans del salt }; struct LogoLetter { std::shared_ptr shape; Vec2 position; float width; float height; float center_offset; }; SDLManager& sdl_; SceneManager::SceneContext& context_; GameConfig::MatchConfig match_config_; Graphics::VectorText text_; Graphics::Curtain curtain_; // Cortinilla negra en saltar a la demo (attract) 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 current_state_{TitleState::STARFIELD_FADE_IN}; float temps_acumulat_{0.0F}; float idle_timer_{0.0F}; // Attract mode: inactivitat acumulada al state MAIN // Dive (attract): temps transcorregut i zoom aplicat als elements 2D // (logo + peu) mentre la càmera avança cap al punt de fuga. float dive_time_{0.0F}; float dive_zoom_{1.0F}; std::vector letters_orni_; std::vector letters_attack_; float dynamic_attack_y_{0.0F}; std::vector letters_jailgames_; float animation_time_{0.0F}; std::vector original_positions_orni_; std::vector original_positions_attack_; float state_time_main_{0.0F}; bool animation_active_{false}; float lerp_factor_{0.0F}; // Progresos de la intro coreografiada al state MAIN. float intro_logo_progress_{0.0F}; float intro_jailgames_progress_{0.0F}; float intro_copyright_progress_{0.0F}; bool press_start_visible_{false}; bool ships_intro_launched_{false}; // Rellotge de fase del parpelleig del "PRESS START". Es reinicia a 0 en cada // transició (aparició del text i pas a parpelleig ràpid) perquè el primer // parpelleig siga sempre un període complet, no un de parcial. float blink_timer_{0.0F}; static constexpr float BRIGHTNESS_STARFIELD = 1.2F; static constexpr float DURATION_FADE_IN = 3.0F; static constexpr float DURATION_INIT = 4.0F; static constexpr float DURATION_TRANSITION = 2.5F; static constexpr float LETTER_SPACING = 10.0F; static constexpr float DURATION_BLACK_SCREEN = 2.0F; static constexpr int MUSIC_FADE = 1500; // Attract mode: temps d'inactivitat al títol (state MAIN) abans de saltar // a la demo. Qualsevol input el reseteja. static constexpr float TITLE_DEMO_TIMEOUT = 20.0F; static constexpr float ORBIT_AMPLITUDE_X = 4.0F; static constexpr float ORBIT_AMPLITUDE_Y = 3.0F; static constexpr float ORBIT_FREQUENCY_X = 0.8F; static constexpr float ORBIT_FREQUENCY_Y = 1.2F; static constexpr float ORBIT_PHASE_OFFSET = 1.57F; static constexpr float SHADOW_DELAY = 0.5F; static constexpr float SHADOW_BRIGHTNESS = 0.4F; static constexpr float SHADOW_OFFSET_X = 2.0F; static constexpr float SHADOW_OFFSET_Y = 2.0F; static constexpr float DURATION_LERP = 2.0F; // Càmera 3D: FOV vertical en radians. static constexpr float CAMERA_FOV_Y_RAD = 1.0472F; // 60° void updateLogoAnimation(float delta_time); static auto checkSkipButtonPressed() -> bool; auto checkStartGameButtonPressed() -> bool; void initTitle(); void inicialitzarJailgames(); void dibuixarPeuTitol(float spacing) const; void updateStarfieldFadeInState(float delta_time); void updateStarfieldState(float delta_time); void updateMainState(float delta_time); void updatePlayerJoinPhaseState(float delta_time); void updateBlackScreenState(float delta_time); void updateDemoDiveState(float delta_time); void updateDemoCurtainState(float delta_time); // Integra el dive (attract): avança la càmera cap a +Z i puja el zoom dels // elements 2D amb acceleració (easeInQuad), sense topall. Es crida tant a // DEMO_DIVE com a DEMO_CURTAIN perquè la càmera no pare mai un cop comença: // segueix accelerant fins i tot sota la cortinilla, fins al salt a la demo. void advanceDive(float delta_time); void handleSkipInput(); void handleStartInput(); void triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_active, const char* log_prefix); };