refactor(pause): elimina pausa F11 (cobreix-la el menú de servei)
This commit is contained in:
1 parent
a8bff31a7c
commit
63a03ae632
11 files changed
+34
-257
No files matched your search
@@ -35,9 +35,6 @@ keys:
|
|||||||
- id: toggle_render_info
|
- id: toggle_render_info
|
||||||
code: "F10"
|
code: "F10"
|
||||||
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
||||||
- id: pause
|
|
||||||
code: "F11"
|
|
||||||
desc: "Pausa el joc (o botó START del mando)"
|
|
||||||
- id: menu_toggle
|
- id: menu_toggle
|
||||||
code: "F12"
|
code: "F12"
|
||||||
desc: "Obri / tanca el menú de servei"
|
desc: "Obri / tanca el menú de servei"
|
||||||
|
|||||||
@@ -114,17 +114,6 @@ notifier:
|
|||||||
slide_in_curve: outCubic
|
slide_in_curve: outCubic
|
||||||
slide_out_curve: inCubic
|
slide_out_curve: inCubic
|
||||||
|
|
||||||
# Pausa graduada de la GameScene: el món no para d'un colp, s'atura amb
|
|
||||||
# easing i torna a arrancar amb una altra corba. Els timings es poden
|
|
||||||
# afinar independentment, i les corbes permeten estils diferents entrant
|
|
||||||
# i sortint (p.ex. frenar suau i arrancar seca). Corbes disponibles:
|
|
||||||
# linear, outQuad, inQuad, inOutQuad, outCubic, inCubic.
|
|
||||||
pause:
|
|
||||||
enter_ms: 400
|
|
||||||
exit_ms: 250
|
|
||||||
enter_curve: outCubic
|
|
||||||
exit_curve: inCubic
|
|
||||||
|
|
||||||
# Sons del menú de servei. Els fitxers viuen a data/audio/sounds/. SELECT
|
# Sons del menú de servei. Els fitxers viuen a data/audio/sounds/. SELECT
|
||||||
# acompanya navegació (amunt/avall) i back; ACCEPT acompanya modificació
|
# acompanya navegació (amunt/avall) i back; ACCEPT acompanya modificació
|
||||||
# d'opció (esquerra/dreta/retorn) o entrada a un submenú.
|
# d'opció (esquerra/dreta/retorn) o entrada a un submenú.
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ input:
|
|||||||
scaling_overscan: "Escalat overscan"
|
scaling_overscan: "Escalat overscan"
|
||||||
scaling_integer: "Escalat enter"
|
scaling_integer: "Escalat enter"
|
||||||
press_again_exit: "Prem una altra volta per eixir"
|
press_again_exit: "Prem una altra volta per eixir"
|
||||||
paused: "Pausa"
|
|
||||||
editor_on: "Editor ON"
|
editor_on: "Editor ON"
|
||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: error guardant el mapa"
|
editor_save_error: "Editor: error guardant el mapa"
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ input:
|
|||||||
scaling_overscan: "Scaling overscan"
|
scaling_overscan: "Scaling overscan"
|
||||||
scaling_integer: "Scaling integer"
|
scaling_integer: "Scaling integer"
|
||||||
press_again_exit: "Press again to exit"
|
press_again_exit: "Press again to exit"
|
||||||
paused: "Paused"
|
|
||||||
editor_on: "Editor ON"
|
editor_on: "Editor ON"
|
||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: map save failed"
|
editor_save_error: "Editor: map save failed"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Configuració de la GameScene: timings i corbes de la FSM de sala
|
# Configuració de la GameScene: timings i corbes de la FSM de sala
|
||||||
# (ROOM_INTRO i ROOM_OUTRO). Tots els valors són ms i tota la geometria
|
# (ROOM_INTRO i ROOM_OUTRO). Tots els valors són ms i tota la geometria
|
||||||
# i font del text es reutilitzen del tema UI (data/config/ui.yaml > pause +
|
# i font del text es reutilitzen del tema UI (data/config/ui.yaml >
|
||||||
# notification). Si manca el fitxer o alguna clau, GameScene cau als
|
# notification). Si manca el fitxer o alguna clau, GameScene cau als
|
||||||
# fallbacks compilats; veure `readSceneConfig` a game_scene.cpp.
|
# fallbacks compilats; veure `readSceneConfig` a game_scene.cpp.
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class Screen {
|
|||||||
// Alpha-blend sobre el canvas: `alpha=0` no fa res, `alpha=255` equival
|
// Alpha-blend sobre el canvas: `alpha=0` no fa res, `alpha=255` equival
|
||||||
// a fillRect. El color entra com a ARGB little-endian; `alpha` és el
|
// a fillRect. El color entra com a ARGB little-endian; `alpha` és el
|
||||||
// factor de mescla amb el que ja hi ha al canvas. Pensat per a overlays
|
// factor de mescla amb el que ja hi ha al canvas. Pensat per a overlays
|
||||||
// (menú, pauses) que volen veure's a través el joc atenuat.
|
// (menú de servei) que volen veure's a través el joc atenuat.
|
||||||
void blendRect(int x, int y, int w, int h, std::uint32_t argb, std::uint8_t alpha);
|
void blendRect(int x, int y, int w, int h, std::uint32_t argb, std::uint8_t alpha);
|
||||||
// Hook invocat al principi de `present()`, abans de pujar els píxels al
|
// Hook invocat al principi de `present()`, abans de pujar els píxels al
|
||||||
// backend. Serveix per a pintar overlays que han de quedar per damunt
|
// backend. Serveix per a pintar overlays que han de quedar per damunt
|
||||||
|
|||||||
@@ -101,10 +101,6 @@ UiConfig::UiConfig()
|
|||||||
hud_shake_ms_(Defaults::UI::HUD_SHAKE_MS),
|
hud_shake_ms_(Defaults::UI::HUD_SHAKE_MS),
|
||||||
hud_blink_cycle_ms_(Defaults::UI::HUD_BLINK_CYCLE_MS),
|
hud_blink_cycle_ms_(Defaults::UI::HUD_BLINK_CYCLE_MS),
|
||||||
hud_blink_visible_ratio_(Defaults::UI::HUD_BLINK_VISIBLE_RATIO),
|
hud_blink_visible_ratio_(Defaults::UI::HUD_BLINK_VISIBLE_RATIO),
|
||||||
pause_enter_ms_(Defaults::UI::PAUSE_ENTER_MS),
|
|
||||||
pause_exit_ms_(Defaults::UI::PAUSE_EXIT_MS),
|
|
||||||
pause_enter_curve_(curveByName(Defaults::UI::PAUSE_ENTER_CURVE)),
|
|
||||||
pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)),
|
|
||||||
title_duration_ms_(Defaults::UI::Title::DURATION_MS),
|
title_duration_ms_(Defaults::UI::Title::DURATION_MS),
|
||||||
title_bg_color_(Defaults::UI::Title::BG_COLOR),
|
title_bg_color_(Defaults::UI::Title::BG_COLOR),
|
||||||
title_text_color_(Defaults::UI::Title::TEXT_COLOR),
|
title_text_color_(Defaults::UI::Title::TEXT_COLOR),
|
||||||
@@ -273,14 +269,6 @@ void UiConfig::load(const std::string& file_path) {
|
|||||||
readField(h, "blink_visible_ratio", hud_blink_visible_ratio_);
|
readField(h, "blink_visible_ratio", hud_blink_visible_ratio_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (yaml.contains("pause")) {
|
|
||||||
const auto& p = yaml["pause"];
|
|
||||||
readField(p, "enter_ms", pause_enter_ms_);
|
|
||||||
readField(p, "exit_ms", pause_exit_ms_);
|
|
||||||
readCurve(p, "enter_curve", pause_enter_curve_);
|
|
||||||
readCurve(p, "exit_curve", pause_exit_curve_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (yaml.contains("sounds")) {
|
if (yaml.contains("sounds")) {
|
||||||
const auto& s = yaml["sounds"];
|
const auto& s = yaml["sounds"];
|
||||||
readField(s, "menu_select", menu_select_sound_);
|
readField(s, "menu_select", menu_select_sound_);
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ class UiConfig {
|
|||||||
[[nodiscard]] auto hudShakeMs() const -> int { return hud_shake_ms_; }
|
[[nodiscard]] auto hudShakeMs() const -> int { return hud_shake_ms_; }
|
||||||
[[nodiscard]] auto hudBlinkCycleMs() const -> int { return hud_blink_cycle_ms_; }
|
[[nodiscard]] auto hudBlinkCycleMs() const -> int { return hud_blink_cycle_ms_; }
|
||||||
[[nodiscard]] auto hudBlinkVisibleRatio() const -> float { return hud_blink_visible_ratio_; }
|
[[nodiscard]] auto hudBlinkVisibleRatio() const -> float { return hud_blink_visible_ratio_; }
|
||||||
[[nodiscard]] auto pauseEnterMs() const -> int { return pause_enter_ms_; }
|
|
||||||
[[nodiscard]] auto pauseExitMs() const -> int { return pause_exit_ms_; }
|
|
||||||
|
|
||||||
// Escena TITLE (placeholder visual fins que tinguem artwork).
|
// Escena TITLE (placeholder visual fins que tinguem artwork).
|
||||||
struct TitleHeatHaze {
|
struct TitleHeatHaze {
|
||||||
@@ -171,11 +169,6 @@ class UiConfig {
|
|||||||
|
|
||||||
[[nodiscard]] auto logoTimeline() const -> const LogoTimeline& { return logo_timeline_; }
|
[[nodiscard]] auto logoTimeline() const -> const LogoTimeline& { return logo_timeline_; }
|
||||||
|
|
||||||
// Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa
|
|
||||||
// Easing::linear com a fallback.
|
|
||||||
[[nodiscard]] auto pauseEnterCurve() const -> CurveFn { return pause_enter_curve_; }
|
|
||||||
[[nodiscard]] auto pauseExitCurve() const -> CurveFn { return pause_exit_curve_; }
|
|
||||||
|
|
||||||
// Resol un nom de corba a la seua funció. Pública per permetre a
|
// Resol un nom de corba a la seua funció. Pública per permetre a
|
||||||
// futurs camps d'UI triar corbes pel mateix mecanisme (ex.: HUD
|
// futurs camps d'UI triar corbes pel mateix mecanisme (ex.: HUD
|
||||||
// entry, notifications slide...). Si el nom no està registrat,
|
// entry, notifications slide...). Si el nom no està registrat,
|
||||||
@@ -242,10 +235,6 @@ class UiConfig {
|
|||||||
int hud_shake_ms_;
|
int hud_shake_ms_;
|
||||||
int hud_blink_cycle_ms_;
|
int hud_blink_cycle_ms_;
|
||||||
float hud_blink_visible_ratio_;
|
float hud_blink_visible_ratio_;
|
||||||
int pause_enter_ms_;
|
|
||||||
int pause_exit_ms_;
|
|
||||||
CurveFn pause_enter_curve_;
|
|
||||||
CurveFn pause_exit_curve_;
|
|
||||||
|
|
||||||
int title_duration_ms_;
|
int title_duration_ms_;
|
||||||
Uint8 title_bg_color_;
|
Uint8 title_bg_color_;
|
||||||
|
|||||||
@@ -484,16 +484,8 @@ GameScene::GameScene(Config config)
|
|||||||
// al Resource::Cache (preload al Director), per tant la primera
|
// al Resource::Cache (preload al Director), per tant la primera
|
||||||
// crida a playMusic és immediata.
|
// crida a playMusic és immediata.
|
||||||
|
|
||||||
// Font de l'overlay de pausa: reutilitzem la Surface ja cachejada al
|
// Font del rètol de transició de sala. Reutilitzem la Surface ja
|
||||||
// Director (PLACEHOLDER_NAME + ".gif"). Text només parseja el .fnt a
|
// cachejada al Director; només construïm el `Text` ací.
|
||||||
// l'arrencada de l'escena, un sol cop.
|
|
||||||
const std::string PAUSE_FONT_KEY = UiConfig::get().placeholderFont().name + ".gif";
|
|
||||||
auto pause_surface = Resource::Cache::get().getSurface(PAUSE_FONT_KEY);
|
|
||||||
pause_font_ = std::make_shared<Text>(pause_surface, UiConfig::get().placeholderFont().fnt);
|
|
||||||
pause_font_->setDefaultKerning(UiConfig::get().placeholderFont().kerning);
|
|
||||||
|
|
||||||
// Font del rètol de transició de sala. Mateixa convenció que la pausa
|
|
||||||
// (Surface preloaded pel Director, només construïm el `Text` ací).
|
|
||||||
const std::string ROOM_TEXT_FONT_KEY = UiConfig::get().roomTextFont().name + ".gif";
|
const std::string ROOM_TEXT_FONT_KEY = UiConfig::get().roomTextFont().name + ".gif";
|
||||||
auto room_text_surface = Resource::Cache::get().getSurface(ROOM_TEXT_FONT_KEY);
|
auto room_text_surface = Resource::Cache::get().getSurface(ROOM_TEXT_FONT_KEY);
|
||||||
room_text_font_ = std::make_shared<Text>(room_text_surface, UiConfig::get().roomTextFont().fnt);
|
room_text_font_ = std::make_shared<Text>(room_text_surface, UiConfig::get().roomTextFont().fnt);
|
||||||
@@ -1413,81 +1405,6 @@ void GameScene::spawnWhip(const WhipSpawnPayload& payload) {
|
|||||||
entity_manager_.add(std::move(whip));
|
entity_manager_.add(std::move(whip));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::handlePauseInput() {
|
|
||||||
#ifdef _DEBUG
|
|
||||||
// Mutex amb l'editor: si està actiu, ignorem la tecla de pausa.
|
|
||||||
if (editor_.active()) { return; }
|
|
||||||
#endif
|
|
||||||
const SDL_Scancode PAUSE_SC = KeyConfig::get().scancode("pause");
|
|
||||||
for (const auto& event : EventBuffer::events) {
|
|
||||||
const bool KEY_HIT = (event.type == SDL_EVENT_KEY_DOWN && PAUSE_SC != SDL_SCANCODE_UNKNOWN && event.key.scancode == PAUSE_SC);
|
|
||||||
if (!KEY_HIT) { continue; }
|
|
||||||
|
|
||||||
// Determinem cap a quin estat anem. Si estem quiets (IDLE o
|
|
||||||
// PAUSED) arranquem la transició contrària. Si estem en marxa
|
|
||||||
// (ENTERING o EXITING) invertim: la `scale` actual es manté com
|
|
||||||
// a `from_scale` nou i la durada es recalcula proporcional a la
|
|
||||||
// distància restant — així mai es produeix un salt de velocitat.
|
|
||||||
const bool GOING_TO_PAUSE = (pause_.state == PauseState::IDLE || pause_.state == PauseState::EXITING);
|
|
||||||
|
|
||||||
// Reprenem l'stream ABANS de començar a pujar el volum: quan
|
|
||||||
// venim de PAUSED l'àudio està aturat de debò (ho fem al final
|
|
||||||
// d'ENTERING, veure updatePauseTransition); reprenem just al
|
|
||||||
// moment de començar EXITING per no perdre el primer frame
|
|
||||||
// d'stream. A les inversions mid-flight (ENTERING → EXITING
|
|
||||||
// sense tocar PAUSED) l'stream no s'ha aturat mai, així que no
|
|
||||||
// cal reprendre.
|
|
||||||
if (!GOING_TO_PAUSE && pause_.state == PauseState::PAUSED) {
|
|
||||||
Audio::get()->resumeMusic();
|
|
||||||
}
|
|
||||||
|
|
||||||
pause_.from_scale = pause_.scale;
|
|
||||||
pause_.to_scale = GOING_TO_PAUSE ? 0.0F : 1.0F;
|
|
||||||
const int TARGET_MS = GOING_TO_PAUSE ? UiConfig::get().pauseEnterMs() : UiConfig::get().pauseExitMs();
|
|
||||||
const float DISTANCE = std::fabs(pause_.to_scale - pause_.from_scale);
|
|
||||||
pause_.duration_ms = std::max(1, static_cast<int>(static_cast<float>(TARGET_MS) * DISTANCE));
|
|
||||||
pause_.elapsed_ms = 0;
|
|
||||||
pause_.curve = GOING_TO_PAUSE ? UiConfig::get().pauseEnterCurve() : UiConfig::get().pauseExitCurve();
|
|
||||||
pause_.state = GOING_TO_PAUSE ? PauseState::ENTERING : PauseState::EXITING;
|
|
||||||
|
|
||||||
Log::info("PAUSE", "{} from_scale={} duration_ms={}", GOING_TO_PAUSE ? "entering" : "exiting", pause_.from_scale, pause_.duration_ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameScene::updatePauseTransition(int delta_ms) {
|
|
||||||
if (pause_.state != PauseState::ENTERING && pause_.state != PauseState::EXITING) { return; }
|
|
||||||
|
|
||||||
pause_.elapsed_ms += delta_ms;
|
|
||||||
bool just_reached_pause = false;
|
|
||||||
if (pause_.elapsed_ms >= pause_.duration_ms) {
|
|
||||||
// Transició completa: enganxem al valor objectiu exacte i
|
|
||||||
// promocionem l'estat al terminal corresponent.
|
|
||||||
pause_.scale = pause_.to_scale;
|
|
||||||
const PauseState NEXT = (pause_.to_scale == 0.0F) ? PauseState::PAUSED : PauseState::IDLE;
|
|
||||||
just_reached_pause = (NEXT == PauseState::PAUSED);
|
|
||||||
pause_.state = NEXT;
|
|
||||||
} else {
|
|
||||||
const float T = static_cast<float>(pause_.elapsed_ms) / static_cast<float>(pause_.duration_ms);
|
|
||||||
const auto CURVE = (pause_.curve != nullptr) ? pause_.curve : &Easing::linear;
|
|
||||||
pause_.scale = std::lerp(pause_.from_scale, pause_.to_scale, CURVE(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fade del volum de música durant la transició. La base és el que
|
|
||||||
// l'usuari ha configurat a Options; multipliquem per la `scale` per
|
|
||||||
// obtenir el volum efectiu aquest frame.
|
|
||||||
const float BASE_VOLUME = Options::audio().music.volume;
|
|
||||||
Audio::get()->setMusicVolume(BASE_VOLUME * pause_.scale);
|
|
||||||
|
|
||||||
// En arribar a PAUSED aturem l'stream: així quan despausem la música
|
|
||||||
// tornarà des d'on s'ha quedat, no des d'on hauria anat a parar
|
|
||||||
// durant el temps que el joc ha estat en pausa. El `resumeMusic` es
|
|
||||||
// fa a `handlePauseInput` al començar l'EXITING, perquè la música
|
|
||||||
// sone ja mentre el volum puja des de 0.
|
|
||||||
if (just_reached_pause) {
|
|
||||||
Audio::get()->pauseMusic();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
namespace {
|
namespace {
|
||||||
auto detectEditorToggle() -> bool {
|
auto detectEditorToggle() -> bool {
|
||||||
@@ -1523,33 +1440,19 @@ namespace {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void GameScene::update(int delta_ms) {
|
void GameScene::update(int delta_ms) {
|
||||||
handlePauseInput();
|
// Congelació instantània mentre el menú de servei està obert: el
|
||||||
|
// món no avança gens. La música segueix sonant perquè el menú serveix
|
||||||
// Congelació instantània mentre el menú de servei està obert. A
|
// entre altres coses per calibrar les opcions d'àudio.
|
||||||
// diferència de la pausa de gameplay (que és un dial amb easing i
|
|
||||||
// fade de música), aquesta és binària: el món no avança gens i la
|
|
||||||
// música no es toca. També posem en pausa el dial de la pausa
|
|
||||||
// regular si estava a mig camí, perquè no continui mentre l'usuari
|
|
||||||
// navega el menú.
|
|
||||||
const bool MENU_FROZEN = ServiceMenu::get().isOpen();
|
const bool MENU_FROZEN = ServiceMenu::get().isOpen();
|
||||||
if (!MENU_FROZEN) {
|
|
||||||
updatePauseTransition(delta_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transició de sala: viu en temps real (no escalada per la pausa)
|
// Transició de sala i sub-FSM de game over: viuen en temps real
|
||||||
// perquè és UI. Avança avans del bloc de món perquè quan PLAYING
|
// perquè són UI. Avancen abans del bloc de món perquè quan PLAYING
|
||||||
// arrenque ja sigui efectiu en el mateix frame.
|
// arrenque ja sigui efectiu en el mateix frame.
|
||||||
updateRoomTransition(delta_ms);
|
updateRoomTransition(delta_ms);
|
||||||
|
|
||||||
// Sub-FSM de game over (CONTINUE? + GAME OVER + fade-to-LOGO).
|
|
||||||
// També en temps real: el countdown de CONTINUE? no es congela
|
|
||||||
// amb la pausa de gameplay. La detecció de "tots fora de partida"
|
|
||||||
// s'arma dins quan estem a PLAYING.
|
|
||||||
updateGameOverFlow(delta_ms);
|
updateGameOverFlow(delta_ms);
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
// Toggle de l'editor: només si no hi ha cap transició de pausa ni
|
// Toggle de l'editor. L'editor mateix no consumeix la tecla — la
|
||||||
// pausa consolidada. L'editor mateix no consumeix la tecla — la
|
|
||||||
// llegim ací per decidir la coexistència. Tant a l'entrada com a la
|
// llegim ací per decidir la coexistència. Tant a l'entrada com a la
|
||||||
// sortida fem `loadRoom` complet per partir sempre del YAML del
|
// sortida fem `loadRoom` complet per partir sempre del YAML del
|
||||||
// disc: les entitats tornen a la posició inicial (momies, sarcs),
|
// disc: les entitats tornen a la posició inicial (momies, sarcs),
|
||||||
@@ -1557,7 +1460,7 @@ void GameScene::update(int delta_ms) {
|
|||||||
// l'editor sempre veu l'estat-base de la sala (no el d'una partida
|
// l'editor sempre veu l'estat-base de la sala (no el d'una partida
|
||||||
// a meitat) i el joc, en sortir, arrenca fresc amb les edicions ja
|
// a meitat) i el joc, en sortir, arrenca fresc amb les edicions ja
|
||||||
// persistides al YAML.
|
// persistides al YAML.
|
||||||
if (pause_.state == PauseState::IDLE && detectEditorToggle()) {
|
if (!MENU_FROZEN && detectEditorToggle()) {
|
||||||
const bool WAS_ACTIVE = editor_.active();
|
const bool WAS_ACTIVE = editor_.active();
|
||||||
editor_.toggle(tilemap_, entity_manager_);
|
editor_.toggle(tilemap_, entity_manager_);
|
||||||
const bool NOW_ACTIVE = editor_.active();
|
const bool NOW_ACTIVE = editor_.active();
|
||||||
@@ -1575,10 +1478,10 @@ void GameScene::update(int delta_ms) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Salts debug entre sales (`[` / `]`). Mútuament excloents amb la
|
// Salts debug entre sales (`[` / `]`). Mútuament excloents amb el
|
||||||
// pausa i amb l'editor: si qualsevol està actiu, ignorem la tecla
|
// menú de servei i amb l'editor: si qualsevol està actiu, ignorem la
|
||||||
// perquè el salt recarrega entitats i invalidaria l'estat editat.
|
// tecla perquè el salt recarrega entitats i invalidaria l'estat editat.
|
||||||
if (pause_.state == PauseState::IDLE && !editor_.active()) {
|
if (!MENU_FROZEN && !editor_.active()) {
|
||||||
const int DELTA = detectRoomJump();
|
const int DELTA = detectRoomJump();
|
||||||
if (DELTA != 0) {
|
if (DELTA != 0) {
|
||||||
auto& mgr = Levels::LevelManager::get();
|
auto& mgr = Levels::LevelManager::get();
|
||||||
@@ -1592,7 +1495,7 @@ void GameScene::update(int delta_ms) {
|
|||||||
// pintat/sarcs durant proves de progressió. La resta del flux
|
// pintat/sarcs durant proves de progressió. La resta del flux
|
||||||
// (creuar → player_exited → level_complete → OUTRO) es manté.
|
// (creuar → player_exited → level_complete → OUTRO) es manté.
|
||||||
// Mateixos gates que el salt entre sales.
|
// Mateixos gates que el salt entre sales.
|
||||||
if (pause_.state == PauseState::IDLE && !editor_.active() && detectOpenDoor()) {
|
if (!MENU_FROZEN && !editor_.active() && detectOpenDoor()) {
|
||||||
Log::info("DEBUG", "força obertura de la porta");
|
Log::info("DEBUG", "força obertura de la porta");
|
||||||
entity_manager_.forceOpenDoor();
|
entity_manager_.forceOpenDoor();
|
||||||
}
|
}
|
||||||
@@ -1601,33 +1504,31 @@ void GameScene::update(int delta_ms) {
|
|||||||
// Join in progress (regla 1): un slot DISABLED pot entrar a partida
|
// Join in progress (regla 1): un slot DISABLED pot entrar a partida
|
||||||
// pulsant START durant PLAYING. Es processa abans del bloc de món
|
// pulsant START durant PLAYING. Es processa abans del bloc de món
|
||||||
// per que el spawn aeri ja sigui efectiu en aquest mateix frame.
|
// per que el spawn aeri ja sigui efectiu en aquest mateix frame.
|
||||||
// Gateat per pausa/menú/editor: no es vol que un START accidental
|
// Gateat per menú/editor: no es vol que un START accidental mentre
|
||||||
// mentre l'usuari navega per un menú trigueri un join.
|
// l'usuari navega per un menú trigueri un join.
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
const bool EDITOR_OPEN = editor_.active();
|
const bool EDITOR_OPEN = editor_.active();
|
||||||
#else
|
#else
|
||||||
constexpr bool EDITOR_OPEN = false;
|
constexpr bool EDITOR_OPEN = false;
|
||||||
#endif
|
#endif
|
||||||
if (pause_.state == PauseState::IDLE && !MENU_FROZEN && !EDITOR_OPEN) {
|
if (!MENU_FROZEN && !EDITOR_OPEN) {
|
||||||
handleJoinInput();
|
handleJoinInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalInputs::update(delta_ms);
|
GlobalInputs::update(delta_ms);
|
||||||
if (GlobalInputs::wantsQuit()) { wants_quit_ = true; }
|
if (GlobalInputs::wantsQuit()) { wants_quit_ = true; }
|
||||||
|
|
||||||
// El món avança amb `delta_ms * pause_.scale`: scale=1 corre a velocitat
|
// El bloc de món no s'executa si el menú de servei o l'editor de
|
||||||
// real, scale=0 el congela. Quan l'editor de debug està actiu també
|
// debug estan oberts: tots dos congelen el joc instantàniament.
|
||||||
// es salta completament (no l'escalem, el saltem). Notifier,
|
// Notifier, RenderInfo i HUD viuen fora d'aquest bloc perquè s'han
|
||||||
// RenderInfo i HUD reben el delta real, per això viuen fora d'aquest
|
// de seguir actualitzant en aquests casos.
|
||||||
// bloc i no es ralenteixen amb el dial de pausa.
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
const bool EDITOR_FROZEN = editor_.active();
|
const bool EDITOR_FROZEN = editor_.active();
|
||||||
#else
|
#else
|
||||||
constexpr bool EDITOR_FROZEN = false;
|
constexpr bool EDITOR_FROZEN = false;
|
||||||
#endif
|
#endif
|
||||||
const int SCALED_DELTA_MS = static_cast<int>(static_cast<float>(delta_ms) * pause_.scale);
|
if (!EDITOR_FROZEN && !MENU_FROZEN && delta_ms > 0) {
|
||||||
if (!EDITOR_FROZEN && !MENU_FROZEN && SCALED_DELTA_MS > 0) {
|
const float DT = static_cast<float>(delta_ms) / 1000.0F;
|
||||||
const float DT = static_cast<float>(SCALED_DELTA_MS) / 1000.0F;
|
|
||||||
|
|
||||||
// Pipeline de moviment en tres passes per poder intercalar la
|
// Pipeline de moviment en tres passes per poder intercalar la
|
||||||
// resolució de contactes jugador↔jugador entre input i posició:
|
// resolució de contactes jugador↔jugador entre input i posició:
|
||||||
@@ -1791,36 +1692,6 @@ void GameScene::render() {
|
|||||||
editor_.render(tilemap_, entity_manager_);
|
editor_.render(tilemap_, entity_manager_);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Overlay de pausa: text amb stroke blanc (índex 255) centrat amb X
|
|
||||||
// depenent de l'estat del dial. Entra lliscant des de l'esquerra
|
|
||||||
// (ENTERING), es queda al centre (PAUSED) i surt cap a la dreta
|
|
||||||
// (EXITING). La X es deriva directament de `pause_.scale`, que ja
|
|
||||||
// porta la corba aplicada — així text i món frenen alhora.
|
|
||||||
if (pause_.state != PauseState::IDLE && pause_font_) {
|
|
||||||
constexpr Uint8 OUTLINE_WHITE = 255;
|
|
||||||
constexpr Uint8 STROKE_DIST = 1;
|
|
||||||
constexpr int PAUSE_KERNING = 0;
|
|
||||||
const std::string TEXT = Locale::get().text("input.paused");
|
|
||||||
const int TEXT_WIDTH = pause_font_->length(TEXT, PAUSE_KERNING);
|
|
||||||
const int OFF_LEFT = -TEXT_WIDTH;
|
|
||||||
const int OFF_RIGHT = GameCanvas::WIDTH + TEXT_WIDTH;
|
|
||||||
int x = GameCanvas::CENTER_X;
|
|
||||||
if (pause_.state == PauseState::ENTERING) {
|
|
||||||
x = Easing::lerpInt(OFF_LEFT, GameCanvas::CENTER_X, 1.0F - pause_.scale);
|
|
||||||
} else if (pause_.state == PauseState::EXITING) {
|
|
||||||
x = Easing::lerpInt(GameCanvas::CENTER_X, OFF_RIGHT, pause_.scale);
|
|
||||||
}
|
|
||||||
const int Y = GameCanvas::CENTER_Y - (pause_font_->getLineHeight() / 2);
|
|
||||||
pause_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG,
|
|
||||||
x,
|
|
||||||
Y,
|
|
||||||
TEXT,
|
|
||||||
PAUSE_KERNING,
|
|
||||||
UiConfig::get().notificationText(),
|
|
||||||
STROKE_DIST,
|
|
||||||
OUTLINE_WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
Notifier::get().render();
|
Notifier::get().render();
|
||||||
RenderInfo::get().render();
|
RenderInfo::get().render();
|
||||||
Screen::get().present();
|
Screen::get().present();
|
||||||
|
|||||||
@@ -55,24 +55,11 @@ class GameScene : public Scene {
|
|||||||
void setState(RoomFsm::State new_state);
|
void setState(RoomFsm::State new_state);
|
||||||
|
|
||||||
// Avança la transició activa via `RoomFsm::advance`. S'executa cada
|
// Avança la transició activa via `RoomFsm::advance`. S'executa cada
|
||||||
// frame amb delta real (no escalat per la pausa) perquè la transició
|
// frame amb delta real perquè la transició viu en temps de UI. Si
|
||||||
// viu en temps de UI. Si l'estat retornat és diferent de l'actual,
|
// l'estat retornat és diferent de l'actual, crida `setState` (que
|
||||||
// crida `setState` (que reseteja la `Transition`).
|
// reseteja la `Transition`).
|
||||||
void updateRoomTransition(int delta_ms);
|
void updateRoomTransition(int delta_ms);
|
||||||
|
|
||||||
// Consumeix events d'aquest frame (F11 del KeyConfig "pause") per
|
|
||||||
// disparar les transicions de pausa. El mando ja no participa: el
|
|
||||||
// botó START és ara una acció remapeable per dispositiu i el menú
|
|
||||||
// de servei fa de pausa quan cal. No alterna un flag binari: inicia
|
|
||||||
// (o inverteix mid-flight) un "dial" de temps que frena el món amb
|
|
||||||
// easing.
|
|
||||||
void handlePauseInput();
|
|
||||||
|
|
||||||
// Avança la transició activa, recalcula `pause_.scale` i aplica el
|
|
||||||
// fade de volum de música. S'executa cada frame amb el delta real
|
|
||||||
// (no escalat) perquè la transició viu en temps real.
|
|
||||||
void updatePauseTransition(int delta_ms);
|
|
||||||
|
|
||||||
// Spawna un Dust transitori centrat a (`feet_x_center`, `feet_y`).
|
// Spawna un Dust transitori centrat a (`feet_x_center`, `feet_y`).
|
||||||
// Cridat per la subscripció del bus a `player_jumped` /
|
// Cridat per la subscripció del bus a `player_jumped` /
|
||||||
// `player_landed` per pintar el polvet de despegue / aterratge.
|
// `player_landed` per pintar el polvet de despegue / aterratge.
|
||||||
@@ -239,41 +226,11 @@ class GameScene : public Scene {
|
|||||||
// la destrucció.
|
// la destrucció.
|
||||||
std::unique_ptr<Hud> hud_;
|
std::unique_ptr<Hud> hud_;
|
||||||
|
|
||||||
// Pausa de gameplay com a "dial" de temps. En lloc d'un interruptor,
|
|
||||||
// `pause_.scale` va de 1 (món corrent) a 0 (món congelat) amb una
|
|
||||||
// corba d'easing durant ENTERING, i al revés durant EXITING. El
|
|
||||||
// `delta_ms` del món (players + EntityManager) es multiplica per
|
|
||||||
// aquesta escala; HUD, Notifier i GlobalInputs reben sempre el
|
|
||||||
// delta real. Les inversions mid-flight capturen la `scale` actual
|
|
||||||
// com a `from_scale` i escalen la durada a la distància restant per
|
|
||||||
// mantenir la sensació de velocitat. La font és la mateixa que
|
|
||||||
// abans — ja cachejada al Director.
|
|
||||||
std::shared_ptr<Text> pause_font_;
|
|
||||||
|
|
||||||
// Font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO).
|
// Font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO).
|
||||||
// Distinta de `pause_font_` perquè el rètol de fase té estètica
|
// Té estètica pròpia (CaesarDressing per defecte, configurable via
|
||||||
// pròpia (CaesarDressing per defecte, configurable via
|
// `data/config/ui.yaml > fonts.room_text`).
|
||||||
// `data/config/ui.yaml > fonts.room_text`). La pausa segueix amb la
|
|
||||||
// placeholder.
|
|
||||||
std::shared_ptr<Text> room_text_font_;
|
std::shared_ptr<Text> room_text_font_;
|
||||||
|
|
||||||
enum class PauseState : std::uint8_t {
|
|
||||||
IDLE, // Joc corrent; cap transició en marxa. scale == 1.
|
|
||||||
ENTERING, // Frenant cap a la pausa. scale decreix a 0.
|
|
||||||
PAUSED, // Món congelat. scale == 0.
|
|
||||||
EXITING, // Accelerant de tornada. scale creix a 1.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PauseTransition {
|
|
||||||
PauseState state{PauseState::IDLE};
|
|
||||||
float scale{1.0F}; // 0 = parat, 1 = corrent.
|
|
||||||
float from_scale{1.0F}; // Valor de `scale` al començar la transició.
|
|
||||||
float to_scale{1.0F}; // Valor objectiu (0.0 o 1.0).
|
|
||||||
int elapsed_ms{0}; // Temps transcorregut dins la transició.
|
|
||||||
int duration_ms{0}; // Durada total (escalada a la distància).
|
|
||||||
UiConfig::CurveFn curve{nullptr}; // Corba activa.
|
|
||||||
} pause_;
|
|
||||||
|
|
||||||
// Configuració de la FSM de sala (intro/outro). Carregada al ctor des
|
// Configuració de la FSM de sala (intro/outro). Carregada al ctor des
|
||||||
// de `data/scenes/game/game.yaml`; valors per defecte si manca el YAML.
|
// de `data/scenes/game/game.yaml`; valors per defecte si manca el YAML.
|
||||||
RoomFsm::Config transition_config_{};
|
RoomFsm::Config transition_config_{};
|
||||||
@@ -445,11 +402,10 @@ class GameScene : public Scene {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
// Editor modal del tilemap. Mútuament excloent amb la pausa: si la
|
// Editor modal del tilemap. Mútuament excloent amb el menú de servei:
|
||||||
// pausa no està IDLE la tecla de toggle de l'editor s'ignora, i
|
// si el menú està obert, la tecla de toggle de l'editor s'ignora.
|
||||||
// viceversa. Quan `editor_.active()` és true, saltem entitats i
|
// Quan `editor_.active()` és true, saltem entitats i jugador (update
|
||||||
// jugador (update i render) i deixem el tilemap visible perquè es
|
// i render) i deixem el tilemap visible perquè es pugui editar.
|
||||||
// pugui editar.
|
|
||||||
Editor editor_;
|
Editor editor_;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
@@ -252,17 +252,6 @@ namespace Defaults {
|
|||||||
constexpr int HUD_BLINK_CYCLE_MS = 1000;
|
constexpr int HUD_BLINK_CYCLE_MS = 1000;
|
||||||
constexpr float HUD_BLINK_VISIBLE_RATIO = 0.8F;
|
constexpr float HUD_BLINK_VISIBLE_RATIO = 0.8F;
|
||||||
|
|
||||||
// Pausa graduada de la GameScene: el món no para d'un colp, es
|
|
||||||
// frena amb una corba i torna a arrancar amb una altra. Els noms
|
|
||||||
// de corba han de coincidir amb els registrats a
|
|
||||||
// UiConfig::curveByName (linear / outQuad / inQuad / inOutQuad /
|
|
||||||
// outCubic / inCubic). Si un nom no es reconeix, es cau a
|
|
||||||
// Easing::linear.
|
|
||||||
constexpr int PAUSE_ENTER_MS = 400;
|
|
||||||
constexpr int PAUSE_EXIT_MS = 250;
|
|
||||||
constexpr const char* PAUSE_ENTER_CURVE = "outCubic";
|
|
||||||
constexpr const char* PAUSE_EXIT_CURVE = "inCubic";
|
|
||||||
|
|
||||||
// Sons del menú de servei. SELECT acompanya navegació (amunt/avall)
|
// Sons del menú de servei. SELECT acompanya navegació (amunt/avall)
|
||||||
// i back; ACCEPT acompanya modificació d'opció (esq/dret/retorn).
|
// i back; ACCEPT acompanya modificació d'opció (esq/dret/retorn).
|
||||||
// Els fitxers viuen a `data/audio/sounds/`; només es referencia el nom.
|
// Els fitxers viuen a `data/audio/sounds/`; només es referencia el nom.
|
||||||
|
|||||||
Reference in new issue
Block a user