diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 81edc12..0c794c7 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -3,11 +3,15 @@ #include #include +#include #include #include #include +#include +#include "scene.hpp" #include "scene_context.hpp" +#include "global_events.hpp" #include "core/audio/audio.hpp" #include "core/audio/audio_adapter.hpp" #include "core/defaults.hpp" @@ -251,38 +255,69 @@ auto Director::run() -> int { context.setNextScene(SceneType::LOGO); #endif - // Bucle principal de gestió de escenes + // Bucle principal: construir escena → frame loop → destruir → siguiente. while (context.nextScene() != SceneType::EXIT) { - // Sincronitzar SceneManager::actual con context - // (altres sistemes aún poden llegir SceneManager::actual) SceneManager::actual = context.nextScene(); - - switch (context.nextScene()) { - case SceneType::LOGO: { - LogoScene logo(sdl, context); - logo.run(); - break; - } - - case SceneType::TITLE: { - TitleScene titol(sdl, context); - titol.run(); - break; - } - - case SceneType::GAME: { - GameScene juego(sdl, context); - juego.run(); - break; - } - - default: - break; + std::unique_ptr scene = buildScene(context.nextScene(), sdl, context); + if (!scene) { + break; } + runFrameLoop(*scene, sdl, context); } - // Sincronitzar final con SceneManager::actual SceneManager::actual = SceneType::EXIT; - return 0; } + +auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context) + -> std::unique_ptr { + switch (type) { + case SceneType::LOGO: + return std::make_unique(sdl, context); + case SceneType::TITLE: + return std::make_unique(sdl, context); + case SceneType::GAME: + return std::make_unique(sdl, context); + case SceneType::EXIT: + default: + return nullptr; + } +} + +void Director::runFrameLoop(Scene& scene, SDLManager& sdl, SceneContext& context) { + SDL_Event event; + Uint64 last_time = SDL_GetTicks(); + + while (!scene.isFinished()) { + // Delta time real, capeado a 50ms para evitar grandes saltos. + const Uint64 NOW = SDL_GetTicks(); + float delta_time = static_cast(NOW - last_time) / 1000.0F; + last_time = NOW; + delta_time = std::min(delta_time, 0.05F); + + sdl.updateFPS(delta_time); + Mouse::updateCursorVisibility(); + Input::get()->update(); + + // Event loop: primero ventana, después globales, después escena. + while (SDL_PollEvent(&event)) { + if (sdl.handleWindowEvent(event)) { + continue; + } + if (GlobalEvents::handle(event, sdl, context)) { + continue; + } + scene.handleEvent(event); + } + + scene.update(delta_time); + Audio::update(); + sdl.updateColors(delta_time); // no-op desde Fase 8c (oscilación en shader) + + sdl.clear(0, 0, 0); + sdl.updateRenderingContext(); + scene.draw(); + // Hook futuro: overlays globales aquí (FPS+VSync, profilers...). + sdl.present(); + } +} diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index b1ce50f..b71c257 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -1,8 +1,14 @@ #pragma once +#include #include #include +#include "scene_context.hpp" + +class Scene; +class SDLManager; + class Director { public: explicit Director(std::vector const& args); @@ -17,4 +23,16 @@ class Director { static auto checkProgramArguments(std::vector const& args) -> std::string; void createSystemFolder(const std::string& folder); + + // Construye la escena correspondiente al tipo solicitado. Retorna + // nullptr para EXIT u otros valores no constructibles. + static auto buildScene(SceneManager::SceneContext::SceneType type, + SDLManager& sdl, + SceneManager::SceneContext& context) + -> std::unique_ptr; + + // Ejecuta el bucle de frames de UNA escena hasta que scene.isFinished() + // sea true. Maneja delta_time, eventos (globales + escena), update y draw. + static void runFrameLoop(Scene& scene, SDLManager& sdl, + SceneManager::SceneContext& context); }; diff --git a/source/core/system/scene.hpp b/source/core/system/scene.hpp new file mode 100644 index 0000000..96f6038 --- /dev/null +++ b/source/core/system/scene.hpp @@ -0,0 +1,41 @@ +// scene.hpp - Interfaz base para escenas del juego +// © 2025 Orni Attack +// +// El frame loop vive en Director, no en cada escena. Cada escena implementa +// estos cuatro métodos y el Director los llama en orden por frame: +// handleEvent(ev) por cada evento SDL (tras los eventos globales) +// update(dt) lógica +// draw() pintado (entre clear y present del Director) +// isFinished() consultar transición pendiente +// +// Cuando una escena pide transición (vía context_.setNextScene(...)), +// isFinished() debe pasar a true y el Director destruye la escena para +// construir la siguiente. + +#pragma once + +#include + +class Scene { + public: + Scene() = default; + virtual ~Scene() = default; + + Scene(const Scene&) = delete; + auto operator=(const Scene&) -> Scene& = delete; + Scene(Scene&&) = delete; + auto operator=(Scene&&) -> Scene& = delete; + + // Eventos específicos de la escena. Los globales (window resize, + // F1-F4, ESC, QUIT) los procesa GlobalEvents::handle antes. + virtual void handleEvent(const SDL_Event& event) = 0; + + // Lógica de la escena. delta_time en segundos, ya capeado por el Director. + virtual void update(float delta_time) = 0; + + // Pintar la escena (entre clear y present del Director). + virtual void draw() = 0; + + // True cuando la escena ha pedido transición. + [[nodiscard]] virtual auto isFinished() const -> bool = 0; +}; diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 864fa66..e84b565 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -14,12 +14,10 @@ #include "core/audio/audio.hpp" #include "core/entities/entity.hpp" #include "core/input/input.hpp" -#include "core/input/mouse.hpp" #include "core/math/easing.hpp" #include "core/physics/collision.hpp" #include "core/rendering/line_renderer.hpp" #include "core/system/scene_context.hpp" -#include "core/system/global_events.hpp" #include "game/stage_system/stage_loader.hpp" #include "game/systems/collision_system.hpp" #include "game/systems/continue_system.hpp" @@ -64,69 +62,21 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) for (auto& enemy : enemies_) { enemy = Enemy(sdl.getRenderer()); } + + // El resto del estado del juego (física, stages, naves, vidas, puntuación) + // se inicializa en init(), que se llama al final del constructor para que + // la escena esté lista en cuanto el Director la haya construido. + init(); } -void GameScene::run() { - std::cout << "SceneType Juego: Inicialitzant...\n"; +auto GameScene::isFinished() const -> bool { + return context_.nextScene() != SceneType::GAME; +} - // Inicialitzar state del juego - init(); - - SDL_Event event; - Uint64 last_time = SDL_GetTicks(); - - while (SceneManager::actual == SceneType::GAME) { - // Calcular delta_time real - Uint64 current_time = SDL_GetTicks(); - float delta_time = (current_time - last_time) / 1000.0F; - last_time = current_time; - - // Limitar delta_time per evitar grandes salts - delta_time = std::min(delta_time, 0.05F); - - // Actualitzar counter de FPS - sdl_.updateFPS(delta_time); - - // Actualitzar visibilitat del cursor (auto-ocultar) - Mouse::updateCursorVisibility(); - - // Actualitzar sistema de input ABANS del event loop - Input::get()->update(); - - // Processar events SDL - while (SDL_PollEvent(&event)) { - // Manejo de finestra - if (sdl_.handleWindowEvent(event)) { - continue; - } - - // Events globals (F1/F2/F3/ESC/QUIT) - GlobalEvents::handle(event, sdl_, context_); - } - - // Actualitzar física del juego con delta_time real - update(delta_time); - - // Actualitzar sistema de audio - Audio::update(); - - // Actualitzar colors oscil·lats - sdl_.updateColors(delta_time); - - // Netejar pantalla (usa color oscil·lat) - sdl_.clear(0, 0, 0); - - // Actualitzar context de renderizado (factor de scale global) - sdl_.updateRenderingContext(); - - // Dibuixar juego - draw(); - - // Presentar renderer (swap buffers) - sdl_.present(); - } - - std::cout << "SceneType Juego: Finalitzant...\n"; +void GameScene::handleEvent(const SDL_Event& event) { + // GameScene no procesa eventos puntuales SDL: la lógica de input se + // resuelve en update() consultando Input::checkAction. + (void)event; } void GameScene::init() { @@ -346,7 +296,6 @@ auto GameScene::stepGameOver(float delta_time) -> bool { if (game_over_timer_ <= 0.0F) { Audio::get()->stopMusic(); context_.setNextScene(SceneType::TITLE); - SceneManager::actual = SceneType::TITLE; return true; } diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 36b2984..7fe4ee7 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -12,6 +12,7 @@ #include "core/graphics/vector_text.hpp" #include "core/physics/physics_world.hpp" #include "core/rendering/sdl_manager.hpp" +#include "core/system/scene.hpp" #include "core/system/scene_context.hpp" #include "core/system/game_config.hpp" #include "core/types.hpp" @@ -32,15 +33,19 @@ enum class GameOverState : uint8_t { }; // Clase principal del juego (escena) -class GameScene { +class GameScene final : public Scene { public: explicit GameScene(SDLManager& sdl, SceneManager::SceneContext& context); - ~GameScene() = default; + ~GameScene() override = default; - void run(); // Bucle principal de l'escena + // Scene interface + void handleEvent(const SDL_Event& event) override; + void update(float delta_time) override; + void draw() override; + [[nodiscard]] auto isFinished() const -> bool override; + + // Inicialización del estado del juego (llamado por Director tras crear la escena). void init(); - void update(float delta_time); - void draw(); private: SDLManager& sdl_; diff --git a/source/game/scenes/logo_scene.cpp b/source/game/scenes/logo_scene.cpp index f0383aa..283dcea 100644 --- a/source/game/scenes/logo_scene.cpp +++ b/source/game/scenes/logo_scene.cpp @@ -12,10 +12,8 @@ #include "core/audio/audio.hpp" #include "core/graphics/shape_loader.hpp" #include "core/input/input.hpp" -#include "core/input/mouse.hpp" #include "core/rendering/shape_renderer.hpp" #include "core/system/scene_context.hpp" -#include "core/system/global_events.hpp" // Using declarations per simplificar el codi using SceneManager::SceneContext; @@ -69,58 +67,14 @@ LogoScene::~LogoScene() { std::cout << "SceneType Logo: Sons parados\n"; } -void LogoScene::run() { - SDL_Event event; - Uint64 last_time = SDL_GetTicks(); +auto LogoScene::isFinished() const -> bool { + return context_.nextScene() != SceneType::LOGO; +} - while (SceneManager::actual == SceneType::LOGO) { - // Calcular delta_time real - Uint64 current_time = SDL_GetTicks(); - float delta_time = (current_time - last_time) / 1000.0F; - last_time = current_time; - - // Limitar delta_time per evitar grandes salts - delta_time = std::min(delta_time, 0.05F); - - // Actualitzar counter de FPS - sdl_.updateFPS(delta_time); - - // Actualitzar visibilitat del cursor (auto-ocultar) - Mouse::updateCursorVisibility(); - - // Actualitzar sistema de input ABANS del event loop - Input::get()->update(); - - // Processar events SDL - while (SDL_PollEvent(&event)) { - // Manejo de finestra - if (sdl_.handleWindowEvent(event)) { - continue; - } - - // Events globals (F1/F2/F3/ESC/QUIT) - if (GlobalEvents::handle(event, sdl_, context_)) { - continue; - } - - // Processar events de l'escena (qualsevol tecla/clic salta al juego) - processar_events(event); - } - - // Actualitzar lógica - update(delta_time); - - // Actualitzar colors oscil·lats (efecte verd global) - sdl_.updateColors(delta_time); - - // Actualitzar context de renderizado (factor de scale global) - sdl_.updateRenderingContext(); - - // Dibuixar - draw(); - } - - std::cout << "SceneType Logo: Finalitzant...\n"; +void LogoScene::handleEvent(const SDL_Event& event) { + // La lógica de skip se decide en update() consultando el estado de Input; + // aquí no hay eventos puntuales que procesar. + (void)event; } void LogoScene::inicialitzar_lletres() { @@ -314,7 +268,6 @@ void LogoScene::update(float delta_time) { if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) { // Transición a pantalla de título context_.setNextScene(SceneType::TITLE); - SceneManager::actual = SceneType::TITLE; } break; } @@ -322,7 +275,6 @@ void LogoScene::update(float delta_time) { // Verificar botones de skip (SHOOT P1/P2) if (checkSkipButtonPressed()) { context_.setNextScene(SceneType::TITLE, Option::JUMP_TO_TITLE_MAIN); - SceneManager::actual = SceneType::TITLE; } // Actualitzar animaciones de debris @@ -330,13 +282,11 @@ void LogoScene::update(float delta_time) { } void LogoScene::draw() { - // Fons negre - sdl_.clear(0, 0, 0); + // Director ha hecho el clear; aquí solo pintamos lo de la escena. - // PRE_ANIMATION: Solo pantalla negra + // PRE_ANIMATION: Solo pantalla negra (no se pinta nada). if (estat_actual_ == AnimationState::PRE_ANIMATION) { - sdl_.present(); - return; // No renderizar lletres + return; } // ANIMATION o POST_ANIMATION: Dibuixar lletres con animación @@ -411,14 +361,8 @@ void LogoScene::draw() { // Siempre draw debris (si n'hay de active) debris_manager_->draw(); - - sdl_.present(); } auto LogoScene::checkSkipButtonPressed() -> bool { return Input::get()->checkAnyPlayerAction(ARCADE_BUTTONS); } - -void LogoScene::processar_events(const SDL_Event& event) { - // No procesar eventos genéricos aquí - la lógica se movió a update() -} diff --git a/source/game/scenes/logo_scene.hpp b/source/game/scenes/logo_scene.hpp index 95fdc13..845edbb 100644 --- a/source/game/scenes/logo_scene.hpp +++ b/source/game/scenes/logo_scene.hpp @@ -14,15 +14,21 @@ #include "core/graphics/shape.hpp" #include "core/input/input_types.hpp" #include "core/rendering/sdl_manager.hpp" +#include "core/system/scene.hpp" #include "core/system/scene_context.hpp" #include "core/types.hpp" #include "game/effects/debris_manager.hpp" -class LogoScene { +class LogoScene final : public Scene { public: explicit LogoScene(SDLManager& sdl, SceneManager::SceneContext& context); - ~LogoScene(); // Destructor per aturar sons - void run(); // Bucle principal de l'escena + ~LogoScene() override; // Destructor per aturar sons + + // Scene interface + void handleEvent(const SDL_Event& event) override; + void update(float delta_time) override; + void draw() override; + [[nodiscard]] auto isFinished() const -> bool override; private: // Màquina de estats per l'animación @@ -79,10 +85,7 @@ class LogoScene { // Métodos privats void inicialitzar_lletres(); - void update(float delta_time); void actualitzar_explosions(float delta_time); - void draw(); - void processar_events(const SDL_Event& event); auto checkSkipButtonPressed() -> bool; // Métodos de gestió de estats diff --git a/source/game/scenes/title_scene.cpp b/source/game/scenes/title_scene.cpp index 00101c0..af8006c 100644 --- a/source/game/scenes/title_scene.cpp +++ b/source/game/scenes/title_scene.cpp @@ -13,10 +13,8 @@ #include "core/audio/audio.hpp" #include "core/graphics/shape_loader.hpp" #include "core/input/input.hpp" -#include "core/input/mouse.hpp" #include "core/rendering/shape_renderer.hpp" #include "core/system/scene_context.hpp" -#include "core/system/global_events.hpp" #include "project.h" // Using declarations per simplificar el codi @@ -256,67 +254,8 @@ void TitleScene::inicialitzar_titol() { std::cout << "[TitleScene] Animación: Posicions originals guardades\n"; } -void TitleScene::run() { - SDL_Event event; - Uint64 last_time = SDL_GetTicks(); - - while (SceneManager::actual == SceneType::TITLE) { - // Calcular delta_time real - Uint64 current_time = SDL_GetTicks(); - float delta_time = (current_time - last_time) / 1000.0F; - last_time = current_time; - - // Limitar delta_time per evitar grandes salts - delta_time = std::min(delta_time, 0.05F); - - // Actualitzar counter de FPS - sdl_.updateFPS(delta_time); - - // Actualitzar visibilitat del cursor (auto-ocultar) - Mouse::updateCursorVisibility(); - - // Actualitzar sistema de input ABANS del event loop - Input::get()->update(); - - // Processar events SDL - while (SDL_PollEvent(&event)) { - // Manejo de finestra - if (sdl_.handleWindowEvent(event)) { - continue; - } - - // Events globals (F1/F2/F3/F4/ESC/QUIT) - if (GlobalEvents::handle(event, sdl_, context_)) { - continue; - } - - // Processar events de l'escena - processar_events(event); - } - - // Actualitzar lógica - update(delta_time); - - // Actualitzar sistema de audio - Audio::update(); - - // Actualitzar colors oscil·lats - sdl_.updateColors(delta_time); - - // Netejar pantalla - sdl_.clear(0, 0, 0); - - // Actualitzar context de renderizado (factor de scale global) - sdl_.updateRenderingContext(); - - // Dibuixar - draw(); - - // Presentar renderer (swap buffers) - sdl_.present(); - } - - std::cout << "SceneType Titol: Finalitzant...\n"; +auto TitleScene::isFinished() const -> bool { + return context_.nextScene() != SceneType::TITLE; } void TitleScene::update(float delta_time) { @@ -449,8 +388,8 @@ void TitleScene::update(float delta_time) { // No animation, no input checking - just wait if (temps_acumulat_ >= DURACIO_BLACK_SCREEN) { - // Transición a escena GAME - SceneManager::actual = SceneType::GAME; + // Transición a escena GAME (el Director detecta isFinished()). + context_.setNextScene(SceneType::GAME); std::cout << "[TitleScene] Canviant a escena GAME\n"; } break; @@ -489,7 +428,8 @@ void TitleScene::update(float delta_time) { << (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU") << '\n'; - context_.setNextScene(SceneType::GAME); + // El setNextScene a GAME se hace al final de BLACK_SCREEN para no + // saltar la animación de salida (isFinished() lo recoge entonces). estat_actual_ = TitleState::PLAYER_JOIN_PHASE; temps_acumulat_ = 0.0F; @@ -725,6 +665,8 @@ auto TitleScene::checkStartGameButtonPressed() -> bool { return any_pressed; } -void TitleScene::processar_events(const SDL_Event& event) { - // No procesar eventos genéricos aquí - la lógica se movió a update() +void TitleScene::handleEvent(const SDL_Event& event) { + // La lógica de input se decide en update() consultando Input::checkAction; + // aquí no hay eventos puntuales que procesar. + (void)event; } diff --git a/source/game/scenes/title_scene.hpp b/source/game/scenes/title_scene.hpp index cd8ad91..826da38 100644 --- a/source/game/scenes/title_scene.hpp +++ b/source/game/scenes/title_scene.hpp @@ -16,6 +16,7 @@ #include "core/graphics/vector_text.hpp" #include "core/input/input_types.hpp" #include "core/rendering/sdl_manager.hpp" +#include "core/system/scene.hpp" #include "core/system/scene_context.hpp" #include "core/system/game_config.hpp" #include "core/types.hpp" @@ -25,11 +26,16 @@ static constexpr std::array START_GAME_BUTTONS = { InputAction::START}; -class TitleScene { +class TitleScene final : public Scene { public: explicit TitleScene(SDLManager& sdl, SceneManager::SceneContext& context); - ~TitleScene(); // Destructor per aturar música - void run(); // Bucle principal de l'escena + ~TitleScene() override; // Destructor per aturar música + + // Scene interface + void handleEvent(const SDL_Event& event) override; + void update(float delta_time) override; + void draw() override; + [[nodiscard]] auto isFinished() const -> bool override; private: // Màquina de estats per la pantalla de título @@ -102,10 +108,7 @@ class TitleScene { static constexpr float DURACIO_LERP = 2.0F; // 2s per arribar a amplitud completa // Métodos privats - void update(float delta_time); 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 las lletres del título