feat(game): skip de countdown amb JUMP/ATTACK quan numero <= 7
This commit is contained in:
1 parent
a717411edd
commit
486a440668
7 files changed
+115
-12
No files matched your search
@@ -85,3 +85,9 @@ game_over:
|
||||
# (al primer cop que es mostra `continue_count_start`, a cada canvi,
|
||||
# i quan apareix el 0 — però NO al saltar a GAME OVER). Buit = silenci.
|
||||
countdown_sound_key: "countdown.wav"
|
||||
# Llindar a partir del qual qualsevol botó de salt o atac de
|
||||
# qualsevol jugador avança al següent número del countdown.
|
||||
# 7 → skip habilitat mentre el número visible és 7..1 (no a 0).
|
||||
# -1 = mai. Útil per accelerar la decisió un cop la urgència ja
|
||||
# és evident al jugador.
|
||||
skip_below_or_equal: 7
|
||||
@@ -51,6 +51,7 @@ namespace GameOverFsm {
|
||||
if (n.contains("countdown_sound_key")) {
|
||||
cfg.countdown_sound_key = n["countdown_sound_key"].get_value<std::string>();
|
||||
}
|
||||
cfg.skip_below_or_equal = readChildInt(n, "skip_below_or_equal", cfg.skip_below_or_equal);
|
||||
}
|
||||
|
||||
Log::info("GameOverFsm", "timings carregats des de {} (continue hold {}ms, fade {}ms)", yaml_path, cfg.continue_text_hold_ms, cfg.fade_out_ms);
|
||||
@@ -118,6 +119,20 @@ namespace GameOverFsm {
|
||||
t.elapsed_ms = 0;
|
||||
}
|
||||
|
||||
void skipToNextCountdown(State current, Transition& t, const Config& cfg) {
|
||||
if (current != State::CONTINUE_PROMPT) { return; }
|
||||
if (t.phase != 1) { return; }
|
||||
const int START = std::max(0, cfg.continue_count_start);
|
||||
const int STEP_COUNT = START + 1;
|
||||
const int STEP_MS = std::max(1, cfg.continue_text_hold_ms / STEP_COUNT);
|
||||
const int CONSUMED = t.elapsed_ms / STEP_MS;
|
||||
const int CURRENT = std::clamp(START - CONSUMED, 0, START);
|
||||
// No saltem si ja som al 0: el següent "número" és la transició
|
||||
// a GAME_OVER, que `advance` ja gestiona quan el hold s'esgota.
|
||||
if (CURRENT <= 0) { return; }
|
||||
t.elapsed_ms = (CONSUMED + 1) * STEP_MS;
|
||||
}
|
||||
|
||||
auto countdownSecondsRemaining(State current, const Transition& t, const Config& cfg) -> int {
|
||||
if (current != State::CONTINUE_PROMPT) { return 0; }
|
||||
if (t.phase != 1) { return 0; }
|
||||
|
||||
@@ -92,6 +92,14 @@ namespace GameOverFsm {
|
||||
// canvi de número, i quan apareix el 0). NO sona en la
|
||||
// transició posterior cap a GAME_OVER. Buit = sense so.
|
||||
std::string countdown_sound_key{"countdown.wav"};
|
||||
|
||||
// Llindar a partir del qual qualsevol botó de salt o atac (de
|
||||
// qualsevol jugador) salta al següent número del countdown.
|
||||
// Exemple: 7 → mentre el número visible és 7, 6, 5, ..., 1
|
||||
// (no a 0, que ja no té "següent número") un botó d'acció
|
||||
// avança al següent. Si el valor és més gran que `start` el
|
||||
// skip queda desactivat efectivament. -1 = mai.
|
||||
int skip_below_or_equal{7};
|
||||
};
|
||||
|
||||
// Llegeix la configuració des del YAML. Si el fitxer manca o falta
|
||||
@@ -127,6 +135,15 @@ namespace GameOverFsm {
|
||||
// CONTINUE_PROMPT phase 1, retorna 0 (no s'hauria de pintar).
|
||||
[[nodiscard]] auto countdownSecondsRemaining(State current, const Transition& t, const Config& cfg) -> int;
|
||||
|
||||
// Salta al següent número del countdown (decrementa el valor
|
||||
// visible en una unitat). Només actua dins de CONTINUE_PROMPT
|
||||
// phase 1 i quan el valor actual és > 0 — al 0 ja no hi ha
|
||||
// "següent número", la fase ha d'esgotar-se de manera natural i
|
||||
// el caller es recolza en `advance` per al salt a GAME_OVER. La
|
||||
// GameScene comprova també el llindar `skip_below_or_equal`
|
||||
// abans de cridar; aquesta funció, per simplicitat, no en sap.
|
||||
void skipToNextCountdown(State current, Transition& t, const Config& cfg);
|
||||
|
||||
// Nom textual per a logs i tests.
|
||||
[[nodiscard]] auto stateName(State s) -> const char*;
|
||||
|
||||
|
||||
@@ -1231,28 +1231,45 @@ void GameScene::updateGameOverFlow(int delta_ms) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tick del so del countdown: cada vegada que el número visible
|
||||
// canvia (incloent l'aparició del primer valor i del 0), reproduïm
|
||||
// el so configurat. La detecció es fa post-`advance` perquè el
|
||||
// canvi de fase 0 → 1 arribi a aparèixer com a transició -1 → start.
|
||||
// En la transició cap a GAME_OVER el state ja no és CONTINUE_PROMPT
|
||||
// i la branca s'omet — així no sona un "tick" extra al saltar.
|
||||
if (game_over_state_ == GameOverFsm::State::CONTINUE_PROMPT) {
|
||||
// Skip i tick del so del countdown viuen en helpers per acotar
|
||||
// la complexitat cognitiva d'aquesta funció. Skip primer perquè
|
||||
// el tick del so soni amb el valor nou si la pulsació ha avançat.
|
||||
handleCountdownSkip();
|
||||
emitCountdownTickIfChanged();
|
||||
}
|
||||
|
||||
void GameScene::handleCountdownSkip() {
|
||||
if (game_over_state_ != GameOverFsm::State::CONTINUE_PROMPT) { return; }
|
||||
if (game_over_transition_.phase != 1) { return; }
|
||||
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
|
||||
game_over_state_,
|
||||
game_over_transition_,
|
||||
game_over_config_);
|
||||
if (game_over_transition_.phase == 1 && CURRENT != last_countdown_value_) {
|
||||
if (CURRENT <= 0) { return; }
|
||||
if (CURRENT > game_over_config_.skip_below_or_equal) { return; }
|
||||
const bool ANY_ACTION = Input::Detect::pressedActionButton(0) || Input::Detect::pressedActionButton(1);
|
||||
if (!ANY_ACTION) { return; }
|
||||
GameOverFsm::skipToNextCountdown(
|
||||
game_over_state_,
|
||||
game_over_transition_,
|
||||
game_over_config_);
|
||||
}
|
||||
|
||||
void GameScene::emitCountdownTickIfChanged() {
|
||||
if (game_over_state_ != GameOverFsm::State::CONTINUE_PROMPT) { return; }
|
||||
if (game_over_transition_.phase != 1) { return; }
|
||||
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
|
||||
game_over_state_,
|
||||
game_over_transition_,
|
||||
game_over_config_);
|
||||
if (CURRENT == last_countdown_value_) { return; }
|
||||
last_countdown_value_ = CURRENT;
|
||||
const auto& key = game_over_config_.countdown_sound_key;
|
||||
if (!key.empty()) {
|
||||
if (key.empty()) { return; }
|
||||
if (auto* audio = Audio::get(); audio != nullptr) {
|
||||
audio->playSound(key, Audio::Group::INTERFACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::renderGameOverText() {
|
||||
if (!room_text_font_) { return; }
|
||||
|
||||
@@ -341,6 +341,18 @@ class GameScene : public Scene {
|
||||
// marcador.
|
||||
void renderGameOverFade();
|
||||
|
||||
// Comprova si dins de CONTINUE_PROMPT phase 1 el número visible
|
||||
// està al rang de skip i, si un jugador pulsa salt o atac, fa
|
||||
// saltar la fase al següent número via `GameOverFsm::skipToNextCountdown`.
|
||||
// Extret a mètode separat per acotar la complexitat cognitiva
|
||||
// d'`updateGameOverFlow`.
|
||||
void handleCountdownSkip();
|
||||
|
||||
// Detecta canvis de número del countdown i dispara el so
|
||||
// configurat un cop per cada nou valor. També extret per
|
||||
// mantenir `updateGameOverFlow` per sota del llindar de tidy.
|
||||
void emitCountdownTickIfChanged();
|
||||
|
||||
// Helpers d'animació del rètol de transició. Reutilitzats pel
|
||||
// rètol de sala (`renderRoomText`) i pel de game over
|
||||
// (`renderGameOverText`). Reben `phase` i `elapsed_ms` per
|
||||
|
||||
@@ -29,4 +29,31 @@ namespace Input::Detect {
|
||||
});
|
||||
}
|
||||
|
||||
auto pressedActionButton(int player_id) -> bool {
|
||||
// Teclat: les bindings actives venen d'`Options::controls()` (les
|
||||
// que el menú de servei pot reescriure), no de `Defaults` cru —
|
||||
// així respectem el remapping de l'usuari igual que fa el Player.
|
||||
const auto& kb = (player_id == 0) ? Options::controls().keyboard_p1
|
||||
: Options::controls().keyboard_p2;
|
||||
const SDL_Scancode KB_JUMP = kb.jump;
|
||||
const SDL_Scancode KB_ATTACK = kb.attack;
|
||||
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(player_id);
|
||||
const SDL_JoystickID PAD_ID = (pad != nullptr) ? SDL_GetGamepadID(pad) : 0;
|
||||
const auto& slot = (player_id == 0) ? Options::controls().gamepad_p1
|
||||
: Options::controls().gamepad_p2;
|
||||
const int JUMP_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::JUMP);
|
||||
const int ATTACK_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::ATTACK);
|
||||
|
||||
return std::ranges::any_of(EventBuffer::events, [&](const SDL_Event& ev) {
|
||||
if (ev.type == SDL_EVENT_KEY_DOWN && (ev.key.scancode == KB_JUMP || ev.key.scancode == KB_ATTACK)) {
|
||||
return true;
|
||||
}
|
||||
if (ev.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && pad != nullptr && ev.gbutton.which == PAD_ID) {
|
||||
const int BTN = static_cast<int>(ev.gbutton.button);
|
||||
if (BTN == JUMP_BTN || BTN == ATTACK_BTN) { return true; }
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Input::Detect
|
||||
@@ -16,4 +16,13 @@ namespace Input::Detect {
|
||||
// assignat al jugador via `Options::controls()`).
|
||||
[[nodiscard]] auto pressedStart(int player_id) -> bool;
|
||||
|
||||
// Cert si dins dels events SDL d'aquest frame el jugador
|
||||
// `player_id` (0 = P1, 1 = P2) ha pulsat el botó de salt o el
|
||||
// d'atac (qualsevol dels dos). Usat per la GameScene per acceptar
|
||||
// qualsevol "botó d'acció" com a senyal de skip del countdown de
|
||||
// CONTINUE? un cop el número visible cau a un llindar configurable.
|
||||
// Mateix patró que `pressedStart`: combina teclat (Defaults::Keys)
|
||||
// i gamepad (GamepadBindings).
|
||||
[[nodiscard]] auto pressedActionButton(int player_id) -> bool;
|
||||
|
||||
} // namespace Input::Detect
|
||||
Reference in new issue
Block a user