feat(audio): musica acelerada en CONTINUE?, game_over.ogg, tick del countdown
This commit is contained in:
1 parent
5e862ceadc
commit
a717411edd
13 files changed
+186
-2
No files matched your search
@@ -42,6 +42,15 @@ namespace GameOverFsm {
|
||||
cfg.game_over_wait_ms = readChildInt(n, "game_over_wait_ms", cfg.game_over_wait_ms);
|
||||
cfg.fade_out_ms = readChildInt(n, "fade_out_ms", cfg.fade_out_ms);
|
||||
cfg.black_hold_ms = readChildInt(n, "black_hold_ms", cfg.black_hold_ms);
|
||||
if (n.contains("continue_music_speed")) {
|
||||
cfg.continue_music_speed = n["continue_music_speed"].get_value<float>();
|
||||
}
|
||||
if (n.contains("game_over_music_track")) {
|
||||
cfg.game_over_music_track = n["game_over_music_track"].get_value<std::string>();
|
||||
}
|
||||
if (n.contains("countdown_sound_key")) {
|
||||
cfg.countdown_sound_key = n["countdown_sound_key"].get_value<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
Log::info("GameOverFsm", "timings carregats des de {} (continue hold {}ms, fade {}ms)", yaml_path, cfg.continue_text_hold_ms, cfg.fade_out_ms);
|
||||
|
||||
@@ -73,6 +73,25 @@ namespace GameOverFsm {
|
||||
// canvas negre opac abans que el GameScene demani la transició.
|
||||
int fade_out_ms{500};
|
||||
int black_hold_ms{500};
|
||||
|
||||
// Multiplicador de velocitat de la música mentre dura el
|
||||
// CONTINUE_PROMPT. 1.0 = sense canvi, >1 = més ràpid (i amb el
|
||||
// to més agut: és l'efecte arcade clàssic dels comptes enrere).
|
||||
// Es restableix a 1.0 al sortir del prompt (revive o GAME_OVER).
|
||||
float continue_music_speed{1.5F};
|
||||
|
||||
// Pista de música que sona durant l'estat GAME_OVER (sense loop).
|
||||
// Si la pista no està carregada al Resource::Cache, s'omet la
|
||||
// reproducció i la fase manté els timings configurats. La durada
|
||||
// de la pista determina implícitament `game_over_text_hold_ms`
|
||||
// (vegeu `GameScene::setGameOverState`).
|
||||
std::string game_over_music_track{"game_over.ogg"};
|
||||
|
||||
// So que sona cada vegada que apareix un nou número del
|
||||
// countdown (al primer cop que es mostra el `start`, cada
|
||||
// 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"};
|
||||
};
|
||||
|
||||
// Llegeix la configuració des del YAML. Si el fitxer manca o falta
|
||||
|
||||
@@ -506,6 +506,8 @@ GameScene::GameScene(Config config)
|
||||
// pròpia (`game_over:`).
|
||||
game_over_config_ = GameOverFsm::loadConfig(Defaults::GameScene::GAME_YAML);
|
||||
|
||||
preloadGameOverAssets();
|
||||
|
||||
// Cinemàtica d'entrada de la primera sala: arrenca la pista
|
||||
// `room_intro.ogg` i arma el callback de fi que farà spawn + salt
|
||||
// a PLAYING. Cal cridar `enterRoomIntro` ací (i no només des
|
||||
@@ -514,7 +516,18 @@ GameScene::GameScene(Config config)
|
||||
enterRoomIntro();
|
||||
}
|
||||
|
||||
GameScene::~GameScene() = default;
|
||||
GameScene::~GameScene() {
|
||||
// Aturem qualsevol música que estigués sonant al sortir de la
|
||||
// GameScene. Això cobreix tres camins: (a) transició natural a
|
||||
// LOGO al final del game over (l'única audio activa pot ser la
|
||||
// pista `game_over.ogg`, que en aquell moment ja s'ha drenat);
|
||||
// (b) tancament directe via service menu o ALT+F4; (c) qualsevol
|
||||
// futura sortida no-natural. La LogoScene té la seva pròpia
|
||||
// música i no s'ha de mesclar amb la del joc.
|
||||
if (auto* audio = Audio::get(); audio != nullptr) {
|
||||
audio->stopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
auto GameScene::computeSpawn() -> PlayerSpawn {
|
||||
// Centre de la columna del passadís en píxels canvas. La caixa de
|
||||
@@ -711,6 +724,19 @@ void GameScene::onPlayerDisabled(int player_id) {
|
||||
players_active_[player_id] = false;
|
||||
}
|
||||
|
||||
void GameScene::preloadGameOverAssets() const {
|
||||
// Pre-load del so del countdown: el dispara `updateGameOverFlow`
|
||||
// cada vegada que canvia el número visible de CONTINUE? (incloent
|
||||
// l'aparició del 0 — però NO la transició a GAME_OVER). Sense
|
||||
// pre-load, la primera reproducció faria disc amb el countdown ja
|
||||
// en marxa.
|
||||
const auto& key = game_over_config_.countdown_sound_key;
|
||||
if (key.empty()) { return; }
|
||||
Resource::Cache::get().loadSound(
|
||||
key,
|
||||
std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + key);
|
||||
}
|
||||
|
||||
void GameScene::handleJoinInput() {
|
||||
// El "join in progress" es processa durant PLAYING (regla 1) i,
|
||||
// com a cas especial, durant CONTINUE_PROMPT — ahí un START reviu
|
||||
@@ -1112,9 +1138,62 @@ void GameScene::drawRoomTextLine(const std::string& text, int x, int y, std::uin
|
||||
|
||||
void GameScene::setGameOverState(GameOverFsm::State new_state) {
|
||||
if (new_state == game_over_state_) { return; }
|
||||
Log::info("GAMEOVER", "{} -> {}", GameOverFsm::stateName(game_over_state_), GameOverFsm::stateName(new_state));
|
||||
const auto PREV = game_over_state_;
|
||||
Log::info("GAMEOVER", "{} -> {}", GameOverFsm::stateName(PREV), GameOverFsm::stateName(new_state));
|
||||
game_over_state_ = new_state;
|
||||
game_over_transition_ = {};
|
||||
|
||||
auto* audio = Audio::get();
|
||||
|
||||
// Sortida de CONTINUE_PROMPT cap a OFF (revive): reinici de la
|
||||
// música de joc des del principi a velocitat normal. Reaprofitem
|
||||
// el track gravat a `config_.music_track` (sempre `playing.ogg`
|
||||
// amb loop infinit). Si la sortida és cap a GAME_OVER, la branca
|
||||
// de més avall ja talla la música i posa `game_over.ogg` — aquí
|
||||
// saltem perquè el stop+play seria redundant.
|
||||
if (PREV == GameOverFsm::State::CONTINUE_PROMPT && new_state == GameOverFsm::State::OFF && audio != nullptr) {
|
||||
audio->stopMusic();
|
||||
audio->playMusic(config_.music_track, -1);
|
||||
audio->setMusicSpeed(1.0F);
|
||||
}
|
||||
|
||||
if (new_state == GameOverFsm::State::CONTINUE_PROMPT && audio != nullptr) {
|
||||
// Acceleració arcade: stop+play perquè la pista arrenqui des
|
||||
// del principi al ritme accelerat (sense aquest reinici, el
|
||||
// canvi de velocitat sobre la posició actual queda poc
|
||||
// perceptible amb ratios subtils com 1.05). El to també puja:
|
||||
// és l'efecte clàssic dels comptes enrere.
|
||||
audio->stopMusic();
|
||||
audio->playMusic(config_.music_track, -1);
|
||||
audio->setMusicSpeed(game_over_config_.continue_music_speed);
|
||||
}
|
||||
|
||||
// Reset del tracker del so del countdown cada vegada que entrem
|
||||
// a CONTINUE_PROMPT, perquè la primera aparició del valor inicial
|
||||
// (`continue_count_start`) dispari el "tick". Fora de
|
||||
// CONTINUE_PROMPT no s'usa.
|
||||
if (new_state == GameOverFsm::State::CONTINUE_PROMPT) {
|
||||
last_countdown_value_ = -1;
|
||||
}
|
||||
|
||||
if (new_state == GameOverFsm::State::GAME_OVER && audio != nullptr) {
|
||||
// Talla la música actual i arrenca la pista de GAME OVER sense
|
||||
// loop. La fase de hold es redimensiona a la durada real de la
|
||||
// pista perquè el text es mantingui visible mentre dura la
|
||||
// música; l'exit anim, el wait i el fade arrenquen quan la
|
||||
// música ja s'ha drenat.
|
||||
audio->stopMusic();
|
||||
const auto& track = game_over_config_.game_over_music_track;
|
||||
if (!track.empty()) {
|
||||
audio->playMusic(track, 0);
|
||||
const int DUR_MS = Audio::getMusicDurationMs(track);
|
||||
if (DUR_MS > 0) {
|
||||
game_over_config_.game_over_text_hold_ms = DUR_MS;
|
||||
Log::info("GAMEOVER", "{}: durada {} ms (text_hold sincronitzat)", track, DUR_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (new_state == GameOverFsm::State::FADE_OUT) {
|
||||
// Arma el fade en el moment que entrem a la fase de fade-out.
|
||||
// Mateix patró que `outro_fade_started_` per a OUTRO de sala.
|
||||
@@ -1151,6 +1230,28 @@ void GameScene::updateGameOverFlow(int delta_ms) {
|
||||
Log::info("GAMEOVER", "DONE -> requesting LOGO scene");
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
|
||||
game_over_state_,
|
||||
game_over_transition_,
|
||||
game_over_config_);
|
||||
if (game_over_transition_.phase == 1 && CURRENT != last_countdown_value_) {
|
||||
last_countdown_value_ = CURRENT;
|
||||
const auto& key = game_over_config_.countdown_sound_key;
|
||||
if (!key.empty()) {
|
||||
if (auto* audio = Audio::get(); audio != nullptr) {
|
||||
audio->playSound(key, Audio::Group::INTERFACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::renderGameOverText() {
|
||||
|
||||
@@ -140,6 +140,12 @@ class GameScene : public Scene {
|
||||
// mètode per acotar la complexitat cognitiva del subscriber/ctor.
|
||||
void onPlayerDisabled(int player_id);
|
||||
|
||||
// Pre-load dels recursos d'àudio que s'usen mid-partida i no els
|
||||
// toca el Director (so del countdown, etc.). Si la clau és buida
|
||||
// o ja està al cache, és no-op. Extret a mètode per acotar la
|
||||
// complexitat cognitiva del ctor.
|
||||
void preloadGameOverAssets() const;
|
||||
|
||||
// Itera els slots actius i, si el delay configurat ha expirat des
|
||||
// del callback de fi de música d'INTRO, dispara `triggerPlayerSpawn`.
|
||||
// Cridada cada frame durant la fase d'spawn escalonat (entre el
|
||||
@@ -307,6 +313,12 @@ class GameScene : public Scene {
|
||||
Transitions::FadeToBlack game_over_fade_;
|
||||
bool game_over_fade_started_{false};
|
||||
|
||||
// Últim valor del countdown reproduït amb so. -1 com a sentinel
|
||||
// perquè la primera detecció del valor inicial (`continue_count_start`)
|
||||
// dispari el "tick". Es reseteja a -1 cada vegada que entrem a
|
||||
// CONTINUE_PROMPT (vegeu `setGameOverState`).
|
||||
int last_countdown_value_{-1};
|
||||
|
||||
// Avança la sub-FSM de game over consumint `delta_ms`. Detecta la
|
||||
// transició de "tots els jugadors fora de partida" durant PLAYING
|
||||
// (entrada a CONTINUE_PROMPT), arma el fade de FADE_OUT i, en
|
||||
|
||||
Reference in new issue
Block a user