From 8e2865efbd3f6000e7c97403fccfabe3dba325a9 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 26 Apr 2026 18:55:30 +0200 Subject: [PATCH] feat(intro): delay de spawn independent per al segon jugador en mode BOTH --- data/scenes/game.yaml | 3 +- source/game/scenes/game_scene.cpp | 46 +++++++++++++++++++------------ source/game/scenes/game_scene.hpp | 24 ++++++++-------- source/game/scenes/room_fsm.cpp | 3 ++ source/game/scenes/room_fsm.hpp | 8 ++++++ 5 files changed, 55 insertions(+), 29 deletions(-) diff --git a/data/scenes/game.yaml b/data/scenes/game.yaml index aca1ef4..f23a211 100644 --- a/data/scenes/game.yaml +++ b/data/scenes/game.yaml @@ -11,7 +11,8 @@ room_intro: text_hold_ms: 2000 # text quiet al centre text_exit_ms: 400 # text centre → fora per l'esquerra line_gap_px: -4 # px extra entre les dues línies (piràmide / sala) - player_spawn_delay_ms: 2400 # ms des de l'inici d'INTRO al spawn aeri. + player_spawn_delay_ms: 2400 # ms des de l'inici d'INTRO al spawn aeri. + player2_spawn_delay_ms: 2700 # només mode BOTH. Trenca la simultaneïtat. room_outro: text_enter_ms: 400 diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index bccea33..6aab345 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -363,9 +363,10 @@ GameScene::GameScene(Config config) players_.emplace_back(std::make_unique(ANIM_P2, ID_P2, p2_x, SPAWN.y, CONTROLS_P2, Map::makeInfo(), P2_PAINT)); // Tots els slots arrenquen DISABLED. Els actius els reactivarà - // `updateRoomTransition` quan s'esgote `player_spawn_delay_ms_` dins - // de ROOM_INTRO (cinemàtica de caiguda); els inactius es queden - // DISABLED tot el cicle de sala. + // `updateRoomTransition` quan s'esgote el delay corresponent dins + // de ROOM_INTRO (`player_spawn_delay_ms` per al primer, i + // `player2_spawn_delay_ms` per al segon en mode BOTH). Els inactius + // es queden DISABLED tot el cicle de sala. for (auto& p : players_) { p->disable(); } // PaintLayer: font única del pintat del nivell. Es crea després de @@ -535,12 +536,13 @@ void GameScene::enterRoomIntro() { state_ = RoomFsm::State::ROOM_INTRO; transition_ = {}; room_intro_elapsed_ms_ = 0; - players_spawned_in_intro_ = false; + players_spawned_in_intro_.fill(false); } -void GameScene::triggerActivePlayerSpawn() { - if (players_spawned_in_intro_) { return; } - players_spawned_in_intro_ = true; +void GameScene::triggerPlayerSpawn(std::size_t slot_index) { + if (slot_index >= players_spawned_in_intro_.size()) { return; } + if (players_spawned_in_intro_[slot_index]) { return; } + players_spawned_in_intro_[slot_index] = true; // Recalcula spawn per si door_col canvia entre sales (avui no, però // la lògica ja queda preparada per a l'itinerari). @@ -551,14 +553,11 @@ void GameScene::triggerActivePlayerSpawn() { // amb un sol actiu, el reposicionem al centre exacte. const float SINGLE_X = SPAWN.p1_x + static_cast(Map::tile_size); - for (std::size_t i = 0; i < players_.size(); ++i) { - if (!players_starts_active_[i]) { continue; } - float x = SINGLE_X; - if (BOTH_ACTIVE) { x = (i == 0) ? SPAWN.p1_x : SPAWN.p2_x; } - players_[i]->spawnFromAir(x, SPAWN.y); - } + float x = SINGLE_X; + if (BOTH_ACTIVE) { x = (slot_index == 0) ? SPAWN.p1_x : SPAWN.p2_x; } + players_[slot_index]->spawnFromAir(x, SPAWN.y); - std::cout << "[ROOM] spawn cinematic: airborne entry for active players\n"; + std::cout << "[ROOM] spawn cinematic P" << (slot_index + 1) << ": airborne entry\n"; } void GameScene::setState(RoomFsm::State new_state) { @@ -570,7 +569,7 @@ void GameScene::setState(RoomFsm::State new_state) { // l'spawn aeri perquè es torne a disparar al delay configurat. if (new_state == RoomFsm::State::ROOM_INTRO) { room_intro_elapsed_ms_ = 0; - players_spawned_in_intro_ = false; + players_spawned_in_intro_.fill(false); } // L'avanç de sala (OUTRO → INTRO) NO viu ací: el dispara // `updateRoomTransition` quan la FSM detecta el final de l'OUTRO, @@ -587,8 +586,21 @@ void GameScene::updateRoomTransition(int delta_ms) { // respawn post-mort sense que la FSM de sala hi haja d'intervenir. if (state_ == RoomFsm::State::ROOM_INTRO) { room_intro_elapsed_ms_ += delta_ms; - if (!players_spawned_in_intro_ && room_intro_elapsed_ms_ >= transition_config_.player_spawn_delay_ms) { - triggerActivePlayerSpawn(); + // Cada slot actiu pot caure en un moment distint quan els dos + // estan actius (mode BOTH): P1 amb player_spawn_delay_ms i P2 + // amb player2_spawn_delay_ms. En single-player (P1 o P2 sols) + // l'slot actiu fa servir player_spawn_delay_ms — el segon valor + // queda inactiu, com ha demanat el disseny. + const bool BOTH_ACTIVE = players_starts_active_[0] && players_starts_active_[1]; + for (std::size_t i = 0; i < players_starts_active_.size(); ++i) { + if (!players_starts_active_[i]) { continue; } + if (players_spawned_in_intro_[i]) { continue; } + const int DELAY = (i == 1 && BOTH_ACTIVE) + ? transition_config_.player2_spawn_delay_ms + : transition_config_.player_spawn_delay_ms; + if (room_intro_elapsed_ms_ >= DELAY) { + triggerPlayerSpawn(i); + } } } diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 33cfde9..6803bb6 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -99,19 +99,21 @@ class GameScene : public Scene { std::array players_starts_active_{false, false}; // Mil·lisegons des de l'entrada en ROOM_INTRO, per disparar la - // cinemàtica de spawn aeri quan s'arriba a - // `transition_config_.player_spawn_delay_ms`. Es reseteja a - // `setState(ROOM_INTRO)`. `players_spawned_in_intro_` garanteix - // que el trigger només es dispara una vegada per cicle. + // cinemàtica de spawn aeri quan s'arriba al delay corresponent al + // slot. Es reseteja a `setState(ROOM_INTRO)`. Cada slot té el seu + // propi flag de "ja he caigut" — així podem caure en moments + // distints en mode BOTH (`player_spawn_delay_ms` per a P1 i + // `player2_spawn_delay_ms` per a P2). int room_intro_elapsed_ms_{0}; - bool players_spawned_in_intro_{false}; + std::array players_spawned_in_intro_{false, false}; - // Cinemàtica de spawn: posa els slots actius en AIRBORNE des d'una - // altura fora de pantalla. Cridat per `updateRoomTransition` quan - // `room_intro_elapsed_ms_` supera el delay configurat. Idempotent - // (consulta `players_spawned_in_intro_`). També és el punt al qual - // es connectarà el respawn post-mort en el futur. - void triggerActivePlayerSpawn(); + // Cinemàtica de spawn d'un slot concret. Posa el slot indicat en + // AIRBORNE des d'una altura fora de pantalla. Cridat per + // `updateRoomTransition` quan `room_intro_elapsed_ms_` supera el + // delay específic de l'slot. Idempotent (consulta el flag corresponent + // a `players_spawned_in_intro_`). També és el punt al qual es + // connectarà el respawn post-mort en el futur. + void triggerPlayerSpawn(std::size_t slot_index); // 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 diff --git a/source/game/scenes/room_fsm.cpp b/source/game/scenes/room_fsm.cpp index e6f87e6..a510d93 100644 --- a/source/game/scenes/room_fsm.cpp +++ b/source/game/scenes/room_fsm.cpp @@ -43,6 +43,9 @@ namespace RoomFsm { cfg.intro_text_hold_ms = readChildInt(n, "text_hold_ms", cfg.intro_text_hold_ms); cfg.intro_text_exit_ms = readChildInt(n, "text_exit_ms", cfg.intro_text_exit_ms); cfg.player_spawn_delay_ms = readChildInt(n, "player_spawn_delay_ms", cfg.player_spawn_delay_ms); + // Per defecte el segon jugador cau alhora que el primer. + // El YAML pot trencar la simultaneïtat fixant un valor propi. + cfg.player2_spawn_delay_ms = readChildInt(n, "player2_spawn_delay_ms", cfg.player_spawn_delay_ms); cfg.intro_text_line_gap_px = readChildInt(n, "line_gap_px", cfg.intro_text_line_gap_px); } if (yaml.contains("room_outro")) { diff --git a/source/game/scenes/room_fsm.hpp b/source/game/scenes/room_fsm.hpp index 9b7af74..b10836f 100644 --- a/source/game/scenes/room_fsm.hpp +++ b/source/game/scenes/room_fsm.hpp @@ -57,6 +57,14 @@ namespace RoomFsm { // si el YAML no defineix res — només pensat com a degradació. int player_spawn_delay_ms{500}; + // Delay independent per al segon jugador, **només quan els dos + // slots estan actius** (mode BOTH). En single-player (P1 o P2 + // sols) s'usa sempre `player_spawn_delay_ms` per al jugador + // actiu. Si el YAML no fixa aquest valor, el `loadConfig` el + // copia de `player_spawn_delay_ms` perquè els dos players caiguin + // alhora — comportament històric del joc. + int player2_spawn_delay_ms{500}; + // Píxels extra entre les dues línies del text d'INTRO (piràmide // damunt, sala davall). Es sumen al `lineHeight` natural del // font per separar visualment les dues línies. 0 = stack