#pragma once #include #include #include #include #include "core/scene/scene.hpp" #include "core/ui/ui_config.hpp" #include "game/debug_config.hpp" #include "game/entities/entity_manager.hpp" #include "game/map/paint_layer.hpp" #include "game/map/tilemap.hpp" #ifdef _DEBUG #include "game/editor/editor.hpp" #endif class Hud; class Player; class Text; class GameScene : public Scene { public: // Conjunt de rutes que defineixen una piràmide concreta (mapa + tilemap // + sprites dels jugadors + música). Cada piràmide serà un Config distint; // `defaultConfig()` retorna la primera (Defaults::Pyramid). `starting_players` // controla quins jugadors instancia el ctor: només P1, només P2 o els dos. struct Config { std::string map_yaml; std::string player_sprite; std::string player_yaml; std::string player2_sprite; std::string player2_yaml; std::string music_track; DebugConfig::StartingPlayers starting_players{DebugConfig::StartingPlayers::BOTH}; }; [[nodiscard]] static auto defaultConfig() -> Config; // Estat de l'escena dins del cicle d'una sala. La màquina és interna a // GameScene: SceneManager segueix tractant GameScene com una sola // escena. ROOM_INTRO i ROOM_OUTRO bloquegen input i render dels // jugadors; l'EntityManager segueix avançant i el HUD/Notifier també. // L'enum es declara públic perquè els helpers de log (stateName) i // tests viuen fora de la classe; el camp `state_` segueix privat. enum class State : std::uint8_t { ROOM_INTRO, // Entrada de sala: fade in + text d'entrada. PLAYING, // Joc normal. ROOM_OUTRO, // Sortida de sala: text de sortida + fade out. }; // Configuració de la FSM de sala llegida de `data/scenes/game.yaml`. // Si el YAML manca, els camps queden amb els fallbacks compilats. Es // carrega un sol cop al ctor; canvis al fitxer requereixen reiniciar. // Pública pel mateix motiu que State: el reader viu en un anonymous // namespace fora de la classe per simetria amb els altres readers. struct SceneTransitionConfig { int intro_fade_in_ms{500}; int intro_text_delay_ms{500}; int intro_text_enter_ms{500}; int intro_text_hold_ms{1000}; int intro_text_exit_ms{500}; int outro_text_enter_ms{500}; int outro_text_hold_ms{1000}; int outro_text_exit_ms{500}; int outro_fade_out_ms{500}; UiConfig::CurveFn text_enter_curve{nullptr}; UiConfig::CurveFn text_exit_curve{nullptr}; }; explicit GameScene(Config config = defaultConfig()); ~GameScene() override; void update(int delta_ms) override; void render() override; private: // Punt centralitzat per canviar d'estat. Imprimeix la transició i // reinicialitza els acumuladors de la transició de sala. Cap estat // s'ha de canviar fora d'aquí. void setState(State new_state); // Avança la transició activa (sub-fases, timings). En aquesta fita // les transicions són instantànies (sense efectes); les sub-fases // amb fades i text arribaran als commits següents. S'executa cada // frame amb delta real (no escalat per la pausa) perquè la // transició viu en temps de UI. void updateRoomTransition(int delta_ms); // Consumeix events d'aquest frame (F11 del KeyConfig "pause" o botó // START del gamepad primari) per disparar les transicions de pausa. // No alterna un flag binari: inicia (o inverteix mid-flight) un // "dial" de temps que frena el món amb easing. void handlePauseInput(); // Avança la transició activa, recalcula `pause_.scale` i aplica el // fade de volum de música. S'executa cada frame amb el delta real // (no escalat) perquè la transició viu en temps real. void updatePauseTransition(int delta_ms); // Spawna un Dust transitori centrat a (`feet_x_center`, `feet_y`). // Cridat per la subscripció del bus a `player_jumped` / // `player_landed` per pintar el polvet de despegue / aterratge. // `is_landing` selecciona la variant de l'animació al `Dust`. void spawnDust(float feet_x_center, float feet_y, bool is_landing); Config config_; Tilemap tilemap_; // Gestor d'entitats del nivell (sarcòfags, porta, momia, antorxa, // etc.). Viu aquí perquè la seua vida coincideix amb la de la escena; // cada entitat del nivell es carrega al ctor via loadFromLevel. EntityManager entity_manager_; // Vector des del principi: AEE suportarà multijugador local; cada Player // té els seus propis Controls i entra al vector amb una crida extra. std::vector> players_; // Vista plana de punters crus — la fa servir Player com a llista // d'obstacles mòbils a la callback de col·lisió. Es construeix un sol // cop al ctor (els punters són estables perquè `players_` no es // redimensiona dins de la vida de l'escena). std::vector players_view_; // Factor multiplicatiu del push inter-jugador. Carregat de la clau // `push_factor` del YAML del mapa; 0.30 per defecte (veure // `Defaults::Player::PUSH_FACTOR`). Constant dins de la vida de // l'escena — una piràmide, una física. float push_factor_{0.30F}; // Capa de "pintat" del terra. Escolta `on_tile_entered` al bus, // filtra pintabilitat (sòl transitable) i re-publica `tile_painted` // per als sarcòfags. `unique_ptr` per controlar l'ordre de // construcció/destrucció: es crea al ctor un cop `entity_manager_` i // `tilemap_` estan llestos, i es destrueix abans que `entity_manager_` // (on viu el bus al qual està subscrit). Declarat després d'aquests // dos membres per garantir l'ordre invers a la destrucció. std::unique_ptr paint_layer_; // Marcador de la franja superior (24 px). `unique_ptr` per controlar // l'ordre de construcció/destrucció igual que `paint_layer_`: es crea // al ctor un cop `entity_manager_` ja té el bus, i es destrueix abans // que `entity_manager_` (on viu el bus al qual està subscrit). // Declarat després d'`entity_manager_` per garantir l'ordre invers a // la destrucció. std::unique_ptr hud_; // Pausa de gameplay com a "dial" de temps. En lloc d'un interruptor, // `pause_.scale` va de 1 (món corrent) a 0 (món congelat) amb una // corba d'easing durant ENTERING, i al revés durant EXITING. El // `delta_ms` del món (players + EntityManager) es multiplica per // aquesta escala; HUD, Notifier i GlobalInputs reben sempre el // delta real. Les inversions mid-flight capturen la `scale` actual // com a `from_scale` i escalen la durada a la distància restant per // mantenir la sensació de velocitat. La font és la mateixa que // abans — ja cachejada al Director. std::shared_ptr pause_font_; enum class PauseState : std::uint8_t { IDLE, // Joc corrent; cap transició en marxa. scale == 1. ENTERING, // Frenant cap a la pausa. scale decreix a 0. PAUSED, // Món congelat. scale == 0. EXITING, // Accelerant de tornada. scale creix a 1. }; struct PauseTransition { PauseState state{PauseState::IDLE}; float scale{1.0F}; // 0 = parat, 1 = corrent. float from_scale{1.0F}; // Valor de `scale` al començar la transició. float to_scale{1.0F}; // Valor objectiu (0.0 o 1.0). int elapsed_ms{0}; // Temps transcorregut dins la transició. int duration_ms{0}; // Durada total (escalada a la distància). UiConfig::CurveFn curve{nullptr}; // Corba activa. } pause_; // Configuració de la FSM de sala (intro/outro). Carregada al ctor des // de `data/scenes/game.yaml`; valors per defecte si manca el YAML. SceneTransitionConfig transition_config_{}; // Estat actual de la FSM de sala. S'inicialitza a ROOM_INTRO al ctor // perquè cada vegada que entres al joc passes per la presentació de // la sala. La transició a PLAYING l'orquestra `updateRoomTransition`. State state_{State::ROOM_INTRO}; // Índex de sala que es passa com a paràmetre {n} al text d'intro. // Avui és sempre 1 (no hi ha itinerari de sales encara). Quan // arribi l'itinerari, és el camp que es modificarà al transicionar // de ROOM_OUTRO → ROOM_INTRO per saltar a la pròxima. int room_index_{1}; #ifdef _DEBUG // Editor modal del tilemap. Mútuament excloent amb la pausa: si la // pausa no està IDLE la tecla de toggle de l'editor s'ignora, i // viceversa. Quan `editor_.active()` és true, saltem entitats i // jugador (update i render) i deixem el tilemap visible perquè es // pugui editar. Editor editor_; #endif };