Fase 9d: descomponer GameScene::update en sub-pasos privados
update() pasa de 339 LOC monolitico (cognitive complexity ~137) a
18 LOC orquestadores. Cada seccion logica vive en su propio metodo
privado con responsabilidad unica:
- stepPhysics(dt): physics_world.update + postUpdate.
- stepShootingInput(): SHOOT de P1/P2.
- stepMidGameJoin(): START de jugador inactivo o muerto sin vidas.
- stepContinueScreen(dt): wrapping del Systems::ContinueScreen +
update de fondo. Devuelve true si frame debe terminar.
- stepGameOver(dt): timer final + transicion a TITLE. Devuelve true
si frame debe terminar.
- stepDeathSequence(dt): death timer/respawn/transicion a CONTINUE.
- stepStageStateMachine(dt): despacha a runStage{InitHud,LevelStart,
Playing,LevelCompleted} segun el estado actual.
- runCollisionDetections(): construye el Systems::Collision::Context
y llama detectAll.
GameScene.cpp acumulado tras Fase 9 (a+b+c+d): 1429 -> 1015 LOC.
update() solo: 339 -> 18 LOC. Smoke test xvfb OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+267
-295
@@ -226,10 +226,28 @@ void GameScene::init() {
|
||||
}
|
||||
|
||||
void GameScene::update(float delta_time) {
|
||||
// === FÍSICA: integrar bodies del frame anterior y resolver colisiones ===
|
||||
// Se ejecuta al inicio del frame: las fuerzas aplicadas en el frame N-1
|
||||
// por processInput/AI se integran ahora, y postUpdate sincroniza los
|
||||
// mirrors (center_/angle_) antes de la lógica de juego que los lee.
|
||||
// Orquestador delgado: cada paso vive en su propia función para
|
||||
// mantener update() legible y reducir complejidad cognitiva.
|
||||
stepPhysics(delta_time);
|
||||
|
||||
if (game_over_state_ == GameOverState::NONE) {
|
||||
stepShootingInput();
|
||||
stepMidGameJoin();
|
||||
}
|
||||
if (stepContinueScreen(delta_time)) {
|
||||
return;
|
||||
}
|
||||
if (stepGameOver(delta_time)) {
|
||||
return;
|
||||
}
|
||||
stepDeathSequence(delta_time);
|
||||
stepStageStateMachine(delta_time);
|
||||
}
|
||||
|
||||
void GameScene::stepPhysics(float delta_time) {
|
||||
// Las fuerzas aplicadas en el frame N-1 por processInput/AI se integran
|
||||
// ahora; postUpdate sincroniza los mirrors (center_/angle_) antes de la
|
||||
// lógica de juego que los lee.
|
||||
physics_world_.update(delta_time);
|
||||
for (auto& ship : ships_) {
|
||||
ship.postUpdate(delta_time);
|
||||
@@ -240,331 +258,285 @@ void GameScene::update(float delta_time) {
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.postUpdate(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Processar disparos (state-based, no event-based)
|
||||
if (game_over_state_ == GameOverState::NONE) {
|
||||
auto* input = Input::get();
|
||||
|
||||
// Jugador 1 dispara (solo si está active)
|
||||
if (match_config_.jugador1_actiu) {
|
||||
if (input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
disparar_bala(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Jugador 2 dispara (solo si está active)
|
||||
if (match_config_.jugador2_actiu) {
|
||||
if (input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
disparar_bala(1);
|
||||
}
|
||||
}
|
||||
|
||||
// [FIXED] Allow mid-game join: inactive or dead player presses START
|
||||
// Only during PLAYING state (not INIT_HUD, CONTINUE, GAME_OVER)
|
||||
if (stage_manager_->get_estat() == StageSystem::EstatStage::PLAYING) {
|
||||
// Check if at least one player is alive and playing (game in progress)
|
||||
bool algun_jugador_viu = false;
|
||||
if (match_config_.jugador1_actiu && hit_timer_per_player_[0] != 999.0F) {
|
||||
algun_jugador_viu = true;
|
||||
}
|
||||
if (match_config_.jugador2_actiu && hit_timer_per_player_[1] != 999.0F) {
|
||||
algun_jugador_viu = true;
|
||||
}
|
||||
|
||||
// Only allow join if there's an active game
|
||||
if (algun_jugador_viu) {
|
||||
// P2 can join if not currently playing (never joined OR dead without lives)
|
||||
bool p2_no_juga = !match_config_.jugador2_actiu || // Never joined
|
||||
hit_timer_per_player_[1] == 999.0F; // Dead without lives
|
||||
|
||||
if (p2_no_juga) {
|
||||
if (input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
unir_jugador(1);
|
||||
}
|
||||
}
|
||||
|
||||
// P1 can join if not currently playing (never joined OR dead without lives)
|
||||
bool p1_no_juga = !match_config_.jugador1_actiu || // Never joined
|
||||
hit_timer_per_player_[0] == 999.0F; // Dead without lives
|
||||
|
||||
if (p1_no_juga) {
|
||||
if (input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
unir_jugador(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void GameScene::stepShootingInput() {
|
||||
auto* input = Input::get();
|
||||
if (match_config_.jugador1_actiu &&
|
||||
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
disparar_bala(0);
|
||||
}
|
||||
if (match_config_.jugador2_actiu &&
|
||||
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
disparar_bala(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle CONTINUE screen
|
||||
if (game_over_state_ == GameOverState::CONTINUE) {
|
||||
Systems::ContinueScreen::Context cont_ctx{
|
||||
.state = game_over_state_,
|
||||
.counter = continue_counter_,
|
||||
.tick_timer = continue_tick_timer_,
|
||||
.continues_used = continues_used_,
|
||||
.game_over_timer = game_over_timer_,
|
||||
.lives_per_player = lives_per_player_,
|
||||
.score_per_player = score_per_player_,
|
||||
.hit_timer_per_player = hit_timer_per_player_,
|
||||
.ships = ships_,
|
||||
.match_config = match_config_,
|
||||
.get_spawn_point = [this](uint8_t pid) { return obtenir_punt_spawn(pid); },
|
||||
};
|
||||
Systems::ContinueScreen::update(cont_ctx, delta_time);
|
||||
Systems::ContinueScreen::processInput(cont_ctx);
|
||||
|
||||
// Still update enemies, bullets, and effects during continue screen
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
void GameScene::stepMidGameJoin() {
|
||||
// Permitir join solo durante PLAYING.
|
||||
if (stage_manager_->get_estat() != StageSystem::EstatStage::PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle final GAME OVER state
|
||||
if (game_over_state_ == GameOverState::GAME_OVER) {
|
||||
// Game over: only update timer, enemies, bullets, and debris
|
||||
game_over_timer_ -= delta_time;
|
||||
|
||||
if (game_over_timer_ <= 0.0F) {
|
||||
// Aturar música de juego antes de tornar al título
|
||||
Audio::get()->stopMusic();
|
||||
// Transición a pantalla de título
|
||||
context_.setNextScene(SceneType::TITLE);
|
||||
SceneManager::actual = SceneType::TITLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Enemies and bullets continue moving during game over
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
// Solo se permite join si hay al menos un jugador vivo (no se puede
|
||||
// hacer join en pantalla vacía).
|
||||
const bool ALGU_VIU =
|
||||
(match_config_.jugador1_actiu && hit_timer_per_player_[0] != 999.0F) ||
|
||||
(match_config_.jugador2_actiu && hit_timer_per_player_[1] != 999.0F);
|
||||
if (!ALGU_VIU) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check death sequence state for BOTH players
|
||||
bool algun_jugador_mort = false;
|
||||
auto* input = Input::get();
|
||||
for (uint8_t pid = 0; pid < 2; pid++) {
|
||||
const bool ACTIU = (pid == 0) ? match_config_.jugador1_actiu
|
||||
: match_config_.jugador2_actiu;
|
||||
const bool MUERTO_SIN_VIDAS = hit_timer_per_player_[pid] == 999.0F;
|
||||
if (ACTIU && !MUERTO_SIN_VIDAS) {
|
||||
continue; // jugador ya está jugando
|
||||
}
|
||||
const bool START_PRESSED = (pid == 0)
|
||||
? input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT)
|
||||
: input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
||||
if (START_PRESSED) {
|
||||
unir_jugador(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto GameScene::stepContinueScreen(float delta_time) -> bool {
|
||||
if (game_over_state_ != GameOverState::CONTINUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Systems::ContinueScreen::Context cont_ctx{
|
||||
.state = game_over_state_,
|
||||
.counter = continue_counter_,
|
||||
.tick_timer = continue_tick_timer_,
|
||||
.continues_used = continues_used_,
|
||||
.game_over_timer = game_over_timer_,
|
||||
.lives_per_player = lives_per_player_,
|
||||
.score_per_player = score_per_player_,
|
||||
.hit_timer_per_player = hit_timer_per_player_,
|
||||
.ships = ships_,
|
||||
.match_config = match_config_,
|
||||
.get_spawn_point = [this](uint8_t pid) { return obtenir_punt_spawn(pid); },
|
||||
};
|
||||
Systems::ContinueScreen::update(cont_ctx, delta_time);
|
||||
Systems::ContinueScreen::processInput(cont_ctx);
|
||||
|
||||
// Enemies, bullets y efectos siguen moviéndose en background.
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto GameScene::stepGameOver(float delta_time) -> bool {
|
||||
if (game_over_state_ != GameOverState::GAME_OVER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
game_over_timer_ -= delta_time;
|
||||
if (game_over_timer_ <= 0.0F) {
|
||||
Audio::get()->stopMusic();
|
||||
context_.setNextScene(SceneType::TITLE);
|
||||
SceneManager::actual = SceneType::TITLE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enemies, bullets y efectos siguen moviéndose como fondo.
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto GameScene::stepDeathSequence(float delta_time) -> bool {
|
||||
bool algun_mort = false;
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
if (hit_timer_per_player_[i] > 0.0F && hit_timer_per_player_[i] < 999.0F) {
|
||||
algun_jugador_mort = true;
|
||||
// Death sequence active: update timer
|
||||
hit_timer_per_player_[i] += delta_time;
|
||||
if (hit_timer_per_player_[i] <= 0.0F || hit_timer_per_player_[i] >= 999.0F) {
|
||||
continue;
|
||||
}
|
||||
algun_mort = true;
|
||||
hit_timer_per_player_[i] += delta_time;
|
||||
|
||||
// Check if death duration completed (only trigger ONCE using sentinel value)
|
||||
if (hit_timer_per_player_[i] >= Defaults::Game::DEATH_DURATION) {
|
||||
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
||||
if (hit_timer_per_player_[i] < Defaults::Game::DEATH_DURATION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decrement lives for this player (only once)
|
||||
lives_per_player_[i]--;
|
||||
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
||||
lives_per_player_[i]--;
|
||||
if (lives_per_player_[i] > 0) {
|
||||
Vec2 spawn_pos = obtenir_punt_spawn(i);
|
||||
ships_[i].init(&spawn_pos, /*activar_invulnerabilitat=*/true);
|
||||
hit_timer_per_player_[i] = 0.0F;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lives_per_player_[i] > 0) {
|
||||
// Respawn ship en spawn position con invulnerabilidad
|
||||
Vec2 spawn_pos = obtenir_punt_spawn(i);
|
||||
ships_[i].init(&spawn_pos, true);
|
||||
hit_timer_per_player_[i] = 0.0F;
|
||||
} else {
|
||||
// Player is permanently dead (out of lives)
|
||||
// Set sentinel value to prevent re-entering this block
|
||||
hit_timer_per_player_[i] = 999.0F;
|
||||
|
||||
// Check if ALL ACTIVE players are dead (trigger continue screen)
|
||||
bool p1_dead = !match_config_.jugador1_actiu || lives_per_player_[0] <= 0;
|
||||
bool p2_dead = !match_config_.jugador2_actiu || lives_per_player_[1] <= 0;
|
||||
|
||||
if (p1_dead && p2_dead) {
|
||||
game_over_state_ = GameOverState::CONTINUE;
|
||||
continue_counter_ = Defaults::Game::CONTINUE_COUNT_START;
|
||||
continue_tick_timer_ = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sin vidas: marcar definitivamente muerto y comprobar transición a CONTINUE.
|
||||
hit_timer_per_player_[i] = 999.0F;
|
||||
const bool P1_DEAD = !match_config_.jugador1_actiu || lives_per_player_[0] <= 0;
|
||||
const bool P2_DEAD = !match_config_.jugador2_actiu || lives_per_player_[1] <= 0;
|
||||
if (P1_DEAD && P2_DEAD) {
|
||||
game_over_state_ = GameOverState::CONTINUE;
|
||||
continue_counter_ = Defaults::Game::CONTINUE_COUNT_START;
|
||||
continue_tick_timer_ = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
// If any player is dead, still update enemies/bullets/effects
|
||||
if (algun_jugador_mort) {
|
||||
// Enemies and bullets continue moving during death sequence
|
||||
// Si hay algún muerto, los enemigos/balas/efectos siguen actualizándose
|
||||
// aunque otros jugadores aún jueguen.
|
||||
if (algun_mort) {
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
|
||||
// Don't return - allow alive players to continue playing
|
||||
}
|
||||
return algun_mort;
|
||||
}
|
||||
|
||||
// *** STAGE SYSTEM STATE MACHINE ***
|
||||
|
||||
StageSystem::EstatStage state = stage_manager_->get_estat();
|
||||
|
||||
switch (state) {
|
||||
case StageSystem::EstatStage::INIT_HUD: {
|
||||
// Update stage manager timer (pot canviar l'state!)
|
||||
stage_manager_->update(delta_time);
|
||||
|
||||
// [FIX] Si l'state ha canviat durante update(), salir immediatament
|
||||
// per evitar recalcular la posición de la ship con el nuevo timer
|
||||
if (stage_manager_->get_estat() != StageSystem::EstatStage::INIT_HUD) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Calcular global progress (0.0 al inicio → 1.0 al final)
|
||||
float global_progress = 1.0F - (stage_manager_->get_timer_transicio() / Defaults::Game::INIT_HUD_DURATION);
|
||||
global_progress = std::min(1.0F, global_progress);
|
||||
|
||||
// [NEW] Calcular progress independiente para cada nave
|
||||
float ship1_progress = Systems::InitHud::computeRangeProgress(
|
||||
global_progress,
|
||||
Defaults::Game::INIT_HUD_SHIP1_RATIO_INIT,
|
||||
Defaults::Game::INIT_HUD_SHIP1_RATIO_END);
|
||||
|
||||
float ship2_progress = Systems::InitHud::computeRangeProgress(
|
||||
global_progress,
|
||||
Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
|
||||
Defaults::Game::INIT_HUD_SHIP2_RATIO_END);
|
||||
|
||||
// [MODIFICAT] Animar AMBAS naves con sus progress respectivos
|
||||
if (match_config_.jugador1_actiu && ship1_progress < 1.0F) {
|
||||
Vec2 pos_p1 = Systems::InitHud::computeShipPosition(ship1_progress, obtenir_punt_spawn(0));
|
||||
ships_[0].setCenter(pos_p1);
|
||||
}
|
||||
|
||||
if (match_config_.jugador2_actiu && ship2_progress < 1.0F) {
|
||||
Vec2 pos_p2 = Systems::InitHud::computeShipPosition(ship2_progress, obtenir_punt_spawn(1));
|
||||
ships_[1].setCenter(pos_p2);
|
||||
}
|
||||
|
||||
// Una vez l'animación acaba, permetre control normal
|
||||
// pero mantenir la posición inicial especial hasta LEVEL_START
|
||||
|
||||
void GameScene::stepStageStateMachine(float delta_time) {
|
||||
const StageSystem::EstatStage STATE = stage_manager_->get_estat();
|
||||
switch (STATE) {
|
||||
case StageSystem::EstatStage::INIT_HUD:
|
||||
runStageInitHud(delta_time);
|
||||
break;
|
||||
}
|
||||
|
||||
case StageSystem::EstatStage::LEVEL_START: {
|
||||
// [DEBUG] Log entrada a LEVEL_START
|
||||
static bool first_entry = true;
|
||||
if (first_entry) {
|
||||
std::cout << "[LEVEL_START] ENTERED with P1 pos.y=" << ships_[0].getCenter().y << '\n';
|
||||
first_entry = false;
|
||||
}
|
||||
|
||||
// Update countdown timer
|
||||
stage_manager_->update(delta_time);
|
||||
|
||||
// [NEW] Allow both ships movement and shooting during intro
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
|
||||
if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) { // Only active, alive players
|
||||
ships_[i].processInput(delta_time, i);
|
||||
ships_[i].update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// [NEW] Update bullets
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
|
||||
// [NEW] Update debris
|
||||
debris_manager_.update(delta_time);
|
||||
case StageSystem::EstatStage::LEVEL_START:
|
||||
runStageLevelStart(delta_time);
|
||||
break;
|
||||
}
|
||||
|
||||
case StageSystem::EstatStage::PLAYING: {
|
||||
// [NEW] Update stage manager (spawns enemies, pause if BOTH dead)
|
||||
bool pause_spawn = (hit_timer_per_player_[0] > 0.0F && hit_timer_per_player_[1] > 0.0F);
|
||||
stage_manager_->getSpawnController().update(delta_time, enemies_, pause_spawn);
|
||||
|
||||
// [NEW] Check stage completion (only when at least one player alive)
|
||||
bool algun_jugador_viu = (hit_timer_per_player_[0] == 0.0F || hit_timer_per_player_[1] == 0.0F);
|
||||
if (algun_jugador_viu) {
|
||||
auto& spawn_ctrl = stage_manager_->getSpawnController();
|
||||
if (spawn_ctrl.tots_enemics_destruits(enemies_)) {
|
||||
stage_manager_->stage_completat();
|
||||
Audio::get()->playSound(Defaults::Sound::GOOD_JOB_COMMANDER, Audio::Group::GAME);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// [EXISTING] Normal gameplay - update active players
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
|
||||
if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) { // Only active, alive players
|
||||
ships_[i].processInput(delta_time, i);
|
||||
ships_[i].update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
|
||||
{
|
||||
Systems::Collision::Context col_ctx{
|
||||
.ships = ships_,
|
||||
.enemies = enemies_,
|
||||
.bullets = bullets_,
|
||||
.hit_timer_per_player = hit_timer_per_player_,
|
||||
.score_per_player = score_per_player_,
|
||||
.lives_per_player = lives_per_player_,
|
||||
.debris_manager = debris_manager_,
|
||||
.floating_score_manager = floating_score_manager_,
|
||||
.match_config = match_config_,
|
||||
.on_player_hit = [this](uint8_t pid) { tocado(pid); },
|
||||
};
|
||||
Systems::Collision::detectAll(col_ctx);
|
||||
}
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
case StageSystem::EstatStage::PLAYING:
|
||||
runStagePlaying(delta_time);
|
||||
break;
|
||||
}
|
||||
|
||||
case StageSystem::EstatStage::LEVEL_COMPLETED:
|
||||
// Update countdown timer
|
||||
stage_manager_->update(delta_time);
|
||||
|
||||
// [NEW] Allow both ships movement and shooting during outro
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
|
||||
if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) { // Only active, alive players
|
||||
ships_[i].processInput(delta_time, i);
|
||||
ships_[i].update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// [NEW] Update bullets (allow last shots to continue)
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
|
||||
// [NEW] Update debris (from last destroyed enemies)
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
runStageLevelCompleted(delta_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::runStageInitHud(float delta_time) {
|
||||
// Update stage manager timer (puede cambiar el state).
|
||||
stage_manager_->update(delta_time);
|
||||
// Si el state cambió, salir para no usar el timer del nuevo state.
|
||||
if (stage_manager_->get_estat() != StageSystem::EstatStage::INIT_HUD) {
|
||||
return;
|
||||
}
|
||||
|
||||
float global_progress = 1.0F - (stage_manager_->get_timer_transicio() / Defaults::Game::INIT_HUD_DURATION);
|
||||
global_progress = std::min(1.0F, global_progress);
|
||||
|
||||
const float SHIP1_P = Systems::InitHud::computeRangeProgress(
|
||||
global_progress,
|
||||
Defaults::Game::INIT_HUD_SHIP1_RATIO_INIT,
|
||||
Defaults::Game::INIT_HUD_SHIP1_RATIO_END);
|
||||
const float SHIP2_P = Systems::InitHud::computeRangeProgress(
|
||||
global_progress,
|
||||
Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
|
||||
Defaults::Game::INIT_HUD_SHIP2_RATIO_END);
|
||||
|
||||
if (match_config_.jugador1_actiu && SHIP1_P < 1.0F) {
|
||||
ships_[0].setCenter(Systems::InitHud::computeShipPosition(SHIP1_P, obtenir_punt_spawn(0)));
|
||||
}
|
||||
if (match_config_.jugador2_actiu && SHIP2_P < 1.0F) {
|
||||
ships_[1].setCenter(Systems::InitHud::computeShipPosition(SHIP2_P, obtenir_punt_spawn(1)));
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::runStageLevelStart(float delta_time) {
|
||||
stage_manager_->update(delta_time);
|
||||
|
||||
// Ambas naves pueden moverse y disparar durante el intro.
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
|
||||
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
|
||||
ships_[i].processInput(delta_time, i);
|
||||
ships_[i].update(delta_time);
|
||||
}
|
||||
}
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
debris_manager_.update(delta_time);
|
||||
}
|
||||
|
||||
void GameScene::runStagePlaying(float delta_time) {
|
||||
const bool PAUSE_SPAWN = (hit_timer_per_player_[0] > 0.0F && hit_timer_per_player_[1] > 0.0F);
|
||||
stage_manager_->getSpawnController().update(delta_time, enemies_, PAUSE_SPAWN);
|
||||
|
||||
// Stage completado: cuando al menos un jugador está vivo y todos los enemies muertos.
|
||||
const bool ALGU_VIU = (hit_timer_per_player_[0] == 0.0F || hit_timer_per_player_[1] == 0.0F);
|
||||
if (ALGU_VIU && stage_manager_->getSpawnController().tots_enemics_destruits(enemies_)) {
|
||||
stage_manager_->stage_completat();
|
||||
Audio::get()->playSound(Defaults::Sound::GOOD_JOB_COMMANDER, Audio::Group::GAME);
|
||||
return;
|
||||
}
|
||||
|
||||
// Gameplay normal: ships activos + entidades + colisiones + efectos.
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
|
||||
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
|
||||
ships_[i].processInput(delta_time, i);
|
||||
ships_[i].update(delta_time);
|
||||
}
|
||||
}
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
|
||||
runCollisionDetections();
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
}
|
||||
|
||||
void GameScene::runStageLevelCompleted(float delta_time) {
|
||||
stage_manager_->update(delta_time);
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
|
||||
if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
|
||||
ships_[i].processInput(delta_time, i);
|
||||
ships_[i].update(delta_time);
|
||||
}
|
||||
}
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
debris_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
}
|
||||
|
||||
void GameScene::runCollisionDetections() {
|
||||
Systems::Collision::Context col_ctx{
|
||||
.ships = ships_,
|
||||
.enemies = enemies_,
|
||||
.bullets = bullets_,
|
||||
.hit_timer_per_player = hit_timer_per_player_,
|
||||
.score_per_player = score_per_player_,
|
||||
.lives_per_player = lives_per_player_,
|
||||
.debris_manager = debris_manager_,
|
||||
.floating_score_manager = floating_score_manager_,
|
||||
.match_config = match_config_,
|
||||
.on_player_hit = [this](uint8_t pid) { tocado(pid); },
|
||||
};
|
||||
Systems::Collision::detectAll(col_ctx);
|
||||
}
|
||||
|
||||
void GameScene::draw() {
|
||||
// Handle CONTINUE screen
|
||||
if (game_over_state_ == GameOverState::CONTINUE) {
|
||||
|
||||
@@ -96,4 +96,24 @@ class GameScene {
|
||||
|
||||
// [NEW] Función helper del marcador
|
||||
[[nodiscard]] std::string buildScoreboard() const;
|
||||
|
||||
// Sub-pasos de update() (descompuestos en Fase 9d para reducir
|
||||
// complejidad cognitiva; cada uno es responsable de una sección).
|
||||
void stepPhysics(float delta_time);
|
||||
void stepShootingInput();
|
||||
void stepMidGameJoin();
|
||||
// Devuelven true si el frame debe salir tras esta sección.
|
||||
[[nodiscard]] auto stepContinueScreen(float delta_time) -> bool;
|
||||
[[nodiscard]] auto stepGameOver(float delta_time) -> bool;
|
||||
// Avanza el death timer / respawn / transition a CONTINUE. Devuelve
|
||||
// true si algun jugador está en secuencia de muerte (para que el
|
||||
// caller actualice efectos sin gameplay).
|
||||
[[nodiscard]] auto stepDeathSequence(float delta_time) -> bool;
|
||||
void stepStageStateMachine(float delta_time);
|
||||
void runStageInitHud(float delta_time);
|
||||
void runStageLevelStart(float delta_time);
|
||||
void runStagePlaying(float delta_time);
|
||||
void runStageLevelCompleted(float delta_time);
|
||||
// Helper: ejecuta colisiones de gameplay con el Context preparado.
|
||||
void runCollisionDetections();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user