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,
|
# (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.
|
# i quan apareix el 0 — però NO al saltar a GAME OVER). Buit = silenci.
|
||||||
countdown_sound_key: "countdown.wav"
|
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")) {
|
if (n.contains("countdown_sound_key")) {
|
||||||
cfg.countdown_sound_key = n["countdown_sound_key"].get_value<std::string>();
|
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);
|
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;
|
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 {
|
auto countdownSecondsRemaining(State current, const Transition& t, const Config& cfg) -> int {
|
||||||
if (current != State::CONTINUE_PROMPT) { return 0; }
|
if (current != State::CONTINUE_PROMPT) { return 0; }
|
||||||
if (t.phase != 1) { 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
|
// canvi de número, i quan apareix el 0). NO sona en la
|
||||||
// transició posterior cap a GAME_OVER. Buit = sense so.
|
// transició posterior cap a GAME_OVER. Buit = sense so.
|
||||||
std::string countdown_sound_key{"countdown.wav"};
|
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
|
// 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).
|
// CONTINUE_PROMPT phase 1, retorna 0 (no s'hauria de pintar).
|
||||||
[[nodiscard]] auto countdownSecondsRemaining(State current, const Transition& t, const Config& cfg) -> int;
|
[[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.
|
// Nom textual per a logs i tests.
|
||||||
[[nodiscard]] auto stateName(State s) -> const char*;
|
[[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
|
// Skip i tick del so del countdown viuen en helpers per acotar
|
||||||
// canvia (incloent l'aparició del primer valor i del 0), reproduïm
|
// la complexitat cognitiva d'aquesta funció. Skip primer perquè
|
||||||
// el so configurat. La detecció es fa post-`advance` perquè el
|
// el tick del so soni amb el valor nou si la pulsació ha avançat.
|
||||||
// canvi de fase 0 → 1 arribi a aparèixer com a transició -1 → start.
|
handleCountdownSkip();
|
||||||
// En la transició cap a GAME_OVER el state ja no és CONTINUE_PROMPT
|
emitCountdownTickIfChanged();
|
||||||
// i la branca s'omet — així no sona un "tick" extra al saltar.
|
}
|
||||||
if (game_over_state_ == GameOverFsm::State::CONTINUE_PROMPT) {
|
|
||||||
|
void GameScene::handleCountdownSkip() {
|
||||||
|
if (game_over_state_ != GameOverFsm::State::CONTINUE_PROMPT) { return; }
|
||||||
|
if (game_over_transition_.phase != 1) { return; }
|
||||||
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
|
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
|
||||||
game_over_state_,
|
game_over_state_,
|
||||||
game_over_transition_,
|
game_over_transition_,
|
||||||
game_over_config_);
|
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;
|
last_countdown_value_ = CURRENT;
|
||||||
const auto& key = game_over_config_.countdown_sound_key;
|
const auto& key = game_over_config_.countdown_sound_key;
|
||||||
if (!key.empty()) {
|
if (key.empty()) { return; }
|
||||||
if (auto* audio = Audio::get(); audio != nullptr) {
|
if (auto* audio = Audio::get(); audio != nullptr) {
|
||||||
audio->playSound(key, Audio::Group::INTERFACE);
|
audio->playSound(key, Audio::Group::INTERFACE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameScene::renderGameOverText() {
|
void GameScene::renderGameOverText() {
|
||||||
if (!room_text_font_) { return; }
|
if (!room_text_font_) { return; }
|
||||||
|
|||||||
@@ -341,6 +341,18 @@ class GameScene : public Scene {
|
|||||||
// marcador.
|
// marcador.
|
||||||
void renderGameOverFade();
|
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
|
// Helpers d'animació del rètol de transició. Reutilitzats pel
|
||||||
// rètol de sala (`renderRoomText`) i pel de game over
|
// rètol de sala (`renderRoomText`) i pel de game over
|
||||||
// (`renderGameOverText`). Reben `phase` i `elapsed_ms` per
|
// (`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
|
} // namespace Input::Detect
|
||||||
@@ -16,4 +16,13 @@ namespace Input::Detect {
|
|||||||
// assignat al jugador via `Options::controls()`).
|
// assignat al jugador via `Options::controls()`).
|
||||||
[[nodiscard]] auto pressedStart(int player_id) -> bool;
|
[[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
|
} // namespace Input::Detect
|
||||||
Reference in new issue
Block a user