feat(intro): delay de spawn independent per al segon jugador en mode BOTH

This commit is contained in:
JailDesigner committed 2026-04-26 18:55:30 +02:00
1 parent 99eb42b319
commit 8e2865efbd
5 files changed
+55 -29

No files matched your search

+2 -1
View File
@@ -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
+29 -17
View File
@@ -363,9 +363,10 @@ GameScene::GameScene(Config config)
players_.emplace_back(std::make_unique<Player>(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<float>(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);
}
}
}
+13 -11
View File
@@ -99,19 +99,21 @@ class GameScene : public Scene {
std::array<bool, 2> 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<bool, 2> 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
+3
View File
@@ -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")) {
+8
View File
@@ -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