Fase 9b: extraer ContinueSystem de GameScene
GameScene::actualitzar_continue, processar_input_continue y el
helper check_and_apply_continue_timeout (3 funciones, ~140 LOC) salen
a Systems::ContinueScreen en source/game/systems/continue_system.{hpp,cpp}.
API:
- struct Systems::ContinueScreen::Context: agrupa el estado mutable
(state, counter, tick_timer, continues_used, game_over_timer,
lives/score/hit_timer arrays, ships, match_config) y un callback
get_spawn_point inyectado por GameScene.
- update(ctx, dt): avanza countdown automatico y transiciona a
GAME_OVER si timeout.
- processInput(ctx): START revive jugador(es), THRUST/SHOOT acelera
countdown.
Helpers privados (revivePlayer, checkAndApplyTimeout) en anonymous
namespace del .cpp para evitar contaminar el header.
GameOverState ahora con underlying type explicito (uint8_t) para
permitir forward-declaration limpia en continue_system.hpp.
dibuixar_continue y unir_jugador se quedan en GameScene (render y
gameplay normal, no parte del state machine).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#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"
|
||||
|
||||
// Using declarations per simplificar el codi
|
||||
using SceneManager::SceneContext;
|
||||
@@ -296,8 +297,21 @@ void GameScene::update(float delta_time) {
|
||||
|
||||
// Handle CONTINUE screen
|
||||
if (game_over_state_ == GameOverState::CONTINUE) {
|
||||
actualitzar_continue(delta_time);
|
||||
processar_input_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_) {
|
||||
@@ -1106,116 +1120,6 @@ void GameScene::disparar_bala(uint8_t player_id) {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== CONTINUE & JOIN SYSTEM ====================
|
||||
|
||||
void GameScene::check_and_apply_continue_timeout() {
|
||||
if (continue_counter_ < 0) {
|
||||
game_over_state_ = GameOverState::GAME_OVER;
|
||||
game_over_timer_ = Defaults::Game::GAME_OVER_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::actualitzar_continue(float delta_time) {
|
||||
continue_tick_timer_ -= delta_time;
|
||||
|
||||
if (continue_tick_timer_ <= 0.0F) {
|
||||
continue_counter_--;
|
||||
continue_tick_timer_ = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||
|
||||
// Check if timeout reached (counter < 0)
|
||||
check_and_apply_continue_timeout();
|
||||
|
||||
// Play sound only if still in CONTINUE state (not transitioned to GAME_OVER)
|
||||
if (game_over_state_ == GameOverState::CONTINUE) {
|
||||
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::processar_input_continue() {
|
||||
auto* input = Input::get();
|
||||
|
||||
// Check START for both players
|
||||
bool p1_start = input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
||||
bool p2_start = input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
||||
|
||||
if (p1_start || p2_start) {
|
||||
// Check continue limit (skip if infinite continues)
|
||||
if (!Defaults::Game::INFINITE_CONTINUES && continues_used_ >= Defaults::Game::MAX_CONTINUES) {
|
||||
// Max continues reached → final game over
|
||||
game_over_state_ = GameOverState::GAME_OVER;
|
||||
game_over_timer_ = Defaults::Game::GAME_OVER_DURATION;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only increment if not infinite
|
||||
if (!Defaults::Game::INFINITE_CONTINUES) {
|
||||
continues_used_++;
|
||||
}
|
||||
|
||||
// Determine which player(s) to revive
|
||||
uint8_t player_to_revive = p1_start ? 0 : 1;
|
||||
|
||||
// Reset score and lives (KEEP level and enemies!)
|
||||
score_per_player_[player_to_revive] = 0;
|
||||
lives_per_player_[player_to_revive] = Defaults::Game::STARTING_LIVES;
|
||||
hit_timer_per_player_[player_to_revive] = 0.0F;
|
||||
|
||||
// Activate player if not already
|
||||
if (player_to_revive == 0) {
|
||||
match_config_.jugador1_actiu = true;
|
||||
} else {
|
||||
match_config_.jugador2_actiu = true;
|
||||
}
|
||||
|
||||
// Spawn with invulnerability
|
||||
Vec2 spawn_pos = obtenir_punt_spawn(player_to_revive);
|
||||
ships_[player_to_revive].init(&spawn_pos, true);
|
||||
|
||||
// Check if other player wants to continue too
|
||||
if (p1_start && p2_start) {
|
||||
uint8_t other_player = 1;
|
||||
score_per_player_[other_player] = 0;
|
||||
lives_per_player_[other_player] = Defaults::Game::STARTING_LIVES;
|
||||
hit_timer_per_player_[other_player] = 0.0F;
|
||||
match_config_.jugador2_actiu = true;
|
||||
Vec2 spawn_pos2 = obtenir_punt_spawn(other_player);
|
||||
ships_[other_player].init(&spawn_pos2, true);
|
||||
}
|
||||
|
||||
// Resume game
|
||||
game_over_state_ = GameOverState::NONE;
|
||||
continue_counter_ = 0;
|
||||
continue_tick_timer_ = 0.0F;
|
||||
|
||||
// Play continue confirmation sound
|
||||
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check THRUST/FIRE to accelerate countdown (DO_NOT_ALLOW_REPEAT to avoid spam)
|
||||
bool thrust_p1 = input->checkActionPlayer1(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT);
|
||||
bool thrust_p2 = input->checkActionPlayer2(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT);
|
||||
bool fire_p1 = input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT);
|
||||
bool fire_p2 = input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT);
|
||||
|
||||
if (thrust_p1 || thrust_p2 || fire_p1 || fire_p2) {
|
||||
continue_counter_--;
|
||||
|
||||
// Check if timeout reached (counter < 0)
|
||||
check_and_apply_continue_timeout();
|
||||
|
||||
// Play sound only if still in CONTINUE state
|
||||
if (game_over_state_ == GameOverState::CONTINUE) {
|
||||
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
||||
}
|
||||
|
||||
// Reset timer to prevent double-decrement
|
||||
continue_tick_timer_ = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::dibuixar_continue() {
|
||||
const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
|
||||
constexpr float spacing = 4.0F;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "game/stage_system/stage_manager.hpp"
|
||||
|
||||
// Game over state machine
|
||||
enum class GameOverState {
|
||||
enum class GameOverState : uint8_t {
|
||||
NONE, // Normal gameplay
|
||||
CONTINUE, // Continue countdown screen (9→0)
|
||||
GAME_OVER // Final game over (returning to title)
|
||||
@@ -89,9 +89,6 @@ class GameScene {
|
||||
|
||||
// [NEW] Continue & Join system
|
||||
void unir_jugador(uint8_t player_id); // Join inactive player mid-game
|
||||
void processar_input_continue(); // Handle input during continue screen
|
||||
void actualitzar_continue(float delta_time); // Update continue countdown
|
||||
void check_and_apply_continue_timeout(); // Check if continue timed out and transition to GAME_OVER
|
||||
void dibuixar_continue(); // Draw continue screen
|
||||
|
||||
// [NEW] Stage system helpers
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// continue_system.cpp - Implementación de la pantalla de continue
|
||||
|
||||
#include "game/systems/continue_system.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/audio/audio.hpp"
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/input/input_types.hpp"
|
||||
#include "game/scenes/game_scene.hpp" // GameOverState (definición completa)
|
||||
|
||||
namespace Systems::ContinueScreen {
|
||||
namespace {
|
||||
|
||||
// Si el countdown ha bajado de 0, transiciona a GAME_OVER con su timer.
|
||||
void checkAndApplyTimeout(Context& ctx) {
|
||||
if (ctx.counter < 0) {
|
||||
ctx.state = GameOverState::GAME_OVER;
|
||||
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
void revivePlayer(Context& ctx, uint8_t player_id) {
|
||||
ctx.score_per_player[player_id] = 0;
|
||||
ctx.lives_per_player[player_id] = Defaults::Game::STARTING_LIVES;
|
||||
ctx.hit_timer_per_player[player_id] = 0.0F;
|
||||
|
||||
if (player_id == 0) {
|
||||
ctx.match_config.jugador1_actiu = true;
|
||||
} else {
|
||||
ctx.match_config.jugador2_actiu = true;
|
||||
}
|
||||
|
||||
const Vec2 SPAWN = ctx.get_spawn_point(player_id);
|
||||
ctx.ships[player_id].init(&SPAWN, /*activar_invulnerabilitat=*/true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void update(Context& ctx, float delta_time) {
|
||||
ctx.tick_timer -= delta_time;
|
||||
if (ctx.tick_timer > 0.0F) {
|
||||
return;
|
||||
}
|
||||
ctx.counter--;
|
||||
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||
|
||||
checkAndApplyTimeout(ctx);
|
||||
|
||||
// Solo pita el tick si seguimos en CONTINUE (no transitamos a GAME_OVER)
|
||||
if (ctx.state == GameOverState::CONTINUE) {
|
||||
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
||||
}
|
||||
}
|
||||
|
||||
void processInput(Context& ctx) {
|
||||
auto* input = Input::get();
|
||||
|
||||
const bool P1_START = input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
||||
const bool P2_START = input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
|
||||
|
||||
if (P1_START || P2_START) {
|
||||
// ¿Quedan continues?
|
||||
if (!Defaults::Game::INFINITE_CONTINUES &&
|
||||
ctx.continues_used >= Defaults::Game::MAX_CONTINUES) {
|
||||
ctx.state = GameOverState::GAME_OVER;
|
||||
ctx.game_over_timer = Defaults::Game::GAME_OVER_DURATION;
|
||||
return;
|
||||
}
|
||||
if (!Defaults::Game::INFINITE_CONTINUES) {
|
||||
ctx.continues_used++;
|
||||
}
|
||||
|
||||
const uint8_t PRIMARY = P1_START ? 0 : 1;
|
||||
revivePlayer(ctx, PRIMARY);
|
||||
// Si ambos pulsan START, revivimos a los dos.
|
||||
if (P1_START && P2_START) {
|
||||
revivePlayer(ctx, 1);
|
||||
}
|
||||
|
||||
// Reanudar partida.
|
||||
ctx.state = GameOverState::NONE;
|
||||
ctx.counter = 0;
|
||||
ctx.tick_timer = 0.0F;
|
||||
|
||||
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
|
||||
return;
|
||||
}
|
||||
|
||||
// THRUST/SHOOT acelera el countdown manualmente.
|
||||
const bool ACCEL = input->checkActionPlayer1(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
|
||||
input->checkActionPlayer2(InputAction::THRUST, Input::DO_NOT_ALLOW_REPEAT) ||
|
||||
input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT) ||
|
||||
input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT);
|
||||
|
||||
if (ACCEL) {
|
||||
ctx.counter--;
|
||||
checkAndApplyTimeout(ctx);
|
||||
|
||||
if (ctx.state == GameOverState::CONTINUE) {
|
||||
Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME);
|
||||
}
|
||||
|
||||
ctx.tick_timer = Defaults::Game::CONTINUE_TICK_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Systems::ContinueScreen
|
||||
@@ -0,0 +1,49 @@
|
||||
// continue_system.hpp - Pantalla de continue y máquina de estados de game over
|
||||
// © 2025 Orni Attack
|
||||
//
|
||||
// Gestiona la transición CONTINUE → GAME_OVER, el countdown, los inputs de
|
||||
// los jugadores para continuar la partida y la revivificación. Vive como
|
||||
// estado en GameScene; este módulo solo opera sobre referencias a ese estado.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "core/system/game_config.hpp"
|
||||
#include "core/types.hpp"
|
||||
#include "game/entities/ship.hpp"
|
||||
|
||||
// Forward declaration: GameOverState es un enum class definido en game_scene.hpp.
|
||||
// Para no traer toda la cabecera, lo declaramos aquí.
|
||||
enum class GameOverState : uint8_t;
|
||||
|
||||
namespace Systems::ContinueScreen {
|
||||
|
||||
// Todo lo que el ContinueSystem lee/modifica.
|
||||
struct Context {
|
||||
GameOverState& state;
|
||||
int& counter;
|
||||
float& tick_timer;
|
||||
int& continues_used;
|
||||
float& game_over_timer;
|
||||
std::array<int, 2>& lives_per_player;
|
||||
std::array<int, 2>& score_per_player;
|
||||
std::array<float, 2>& hit_timer_per_player;
|
||||
std::array<Ship, 2>& ships;
|
||||
GameConfig::MatchConfig& match_config;
|
||||
// Helper inyectado por GameScene (obtenir_punt_spawn).
|
||||
std::function<Vec2(uint8_t /*player_id*/)> get_spawn_point;
|
||||
};
|
||||
|
||||
// Avanza el countdown automático (tick interno). Si el contador cae bajo 0,
|
||||
// transiciona a GAME_OVER y arranca el timer final.
|
||||
void update(Context& ctx, float delta_time);
|
||||
|
||||
// Procesa input durante la pantalla CONTINUE:
|
||||
// START → revive al jugador (1 o 2, según quién pulsó).
|
||||
// THRUST/SHOOT → acelera el countdown.
|
||||
void processInput(Context& ctx);
|
||||
|
||||
} // namespace Systems::ContinueScreen
|
||||
Reference in New Issue
Block a user