feat(audio): musica acelerada en CONTINUE?, game_over.ogg, tick del countdown

This commit is contained in:
JailDesigner committed 2026-05-08 22:06:44 +02:00
1 parent 5e862ceadc
commit a717411edd
13 files changed
+186 -2

No files matched your search

+103 -2
View File
@@ -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() {