feat(game-scene): ROOM_OUTRO — text de sortida i fade out del món

This commit is contained in:
JailDesigner committed 2026-04-26 09:02:30 +02:00
1 parent 67b06c0a10
commit 4a9a037cd8
1 file changed
+76 -31
+76 -31
View File
@@ -524,14 +524,25 @@ void GameScene::updateRoomTransition(int delta_ms) {
return; return;
} }
if (state_ == State::ROOM_OUTRO) { if (state_ == State::ROOM_OUTRO) {
// Placeholder fins a l'itinerari de sales: tornem a la mateixa const std::array<int, 4> DURS = {
// sala. No es reinicia res — porta + sarcòfags continuen a transition_config_.outro_text_enter_ms,
// l'estat OPEN, com era l'acord. transition_config_.outro_text_hold_ms,
// TODO(itinerari): seleccionar la pròxima sala (incrementar transition_config_.outro_text_exit_ms,
// `room_index_`, intercanviar `config_.map_yaml`, recrear transition_config_.outro_fade_out_ms,
// tilemap/entitats) abans de tornar a ROOM_INTRO. La };
// implementació de les sub-fases d'OUTRO arribarà al commit 4. while (transition_.phase < static_cast<int>(DURS.size()) && transition_.elapsed_ms >= DURS[transition_.phase]) {
setState(State::ROOM_INTRO); transition_.elapsed_ms -= DURS[transition_.phase];
++transition_.phase;
}
if (transition_.phase >= static_cast<int>(DURS.size())) {
// Placeholder fins a l'itinerari de sales: tornem a la mateixa
// sala. No es reinicia res — porta + sarcòfags continuen a
// l'estat OPEN, com era l'acord.
// TODO(itinerari): seleccionar la pròxima sala (incrementar
// `room_index_`, intercanviar `config_.map_yaml`, recrear
// tilemap/entitats) abans de tornar a ROOM_INTRO.
setState(State::ROOM_INTRO);
}
return; return;
} }
} }
@@ -551,43 +562,77 @@ auto GameScene::formatRoomTitle(int n) -> std::string {
} }
void GameScene::renderRoomFade() { void GameScene::renderRoomFade() {
// Fade negre opac → transparent durant ROOM_INTRO/fase 0. Pintat // Fade negre sobre el món, pintat un cop els actors ja estan al
// sobre el món un cop els actors ja s'han dibuixat. Al commit 4 // canvas i abans del HUD. Dos casos:
// s'afegirà el fade out simètric per a ROOM_OUTRO/fase final. // ROOM_INTRO/fase 0: 255 → 0 (entrada de sala, món emergent)
if (state_ != State::ROOM_INTRO || transition_.phase != 0) { return; } // ROOM_OUTRO/fase 3: 0 → 255 (sortida de sala, món s'apaga)
const int DUR = transition_config_.intro_fade_in_ms; int dur = 0;
const float T = (DUR > 0) float t = 0.0F;
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(DUR), 0.0F, 1.0F) bool fade_in = false;
if (state_ == State::ROOM_INTRO && transition_.phase == 0) {
dur = transition_config_.intro_fade_in_ms;
fade_in = true;
} else if (state_ == State::ROOM_OUTRO && transition_.phase == 3) {
dur = transition_config_.outro_fade_out_ms;
fade_in = false;
} else {
return;
}
t = (dur > 0)
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur), 0.0F, 1.0F)
: 1.0F; : 1.0F;
const auto ALPHA = static_cast<std::uint8_t>(255.0F * (1.0F - T)); const float ALPHA_F = fade_in ? (1.0F - t) : t;
const auto ALPHA = static_cast<std::uint8_t>(255.0F * ALPHA_F);
if (ALPHA == 0) { return; } if (ALPHA == 0) { return; }
Screen::get().blendRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, 0x00000000U, ALPHA); Screen::get().blendRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, 0x00000000U, ALPHA);
} }
void GameScene::renderRoomText() { void GameScene::renderRoomText() {
// Text d'entrada de sala. Visible només durant les sub-fases 2-4 // Text de la transició de sala. Reusa la font cachejada per a la
// de ROOM_INTRO (enter, hold, exit). Reusa la font cachejada per // pausa (mateix tipus de lletra, mateix índex de color), i fa el
// a la pausa: mateix tipus de lletra i mateix índex de color. // mateix patró esquerra → centre → esquerra que el text de pausa
if (state_ != State::ROOM_INTRO || transition_.phase < 2 || transition_.phase > 4) { return; } // però amb les durades pròpies de la transició.
//
// Mapatge per estat (només els estats amb text actiu hi entren):
// ROOM_INTRO: phases 2..4 → enter / hold / exit; text = sala N
// ROOM_OUTRO: phases 0..2 → enter / hold / exit; text = "fase completada"
if (!pause_font_) { return; } if (!pause_font_) { return; }
const std::string TEXT = formatRoomTitle(room_index_); std::string text;
int phase_enter = 0;
int phase_exit = 0;
int dur_enter = 0;
int dur_exit = 0;
if (state_ == State::ROOM_INTRO && transition_.phase >= 2 && transition_.phase <= 4) {
text = formatRoomTitle(room_index_);
phase_enter = 2;
phase_exit = 4;
dur_enter = transition_config_.intro_text_enter_ms;
dur_exit = transition_config_.intro_text_exit_ms;
} else if (state_ == State::ROOM_OUTRO && transition_.phase >= 0 && transition_.phase <= 2) {
text = Locale::get().text("game.room_complete");
phase_enter = 0;
phase_exit = 2;
dur_enter = transition_config_.outro_text_enter_ms;
dur_exit = transition_config_.outro_text_exit_ms;
} else {
return;
}
constexpr int KERNING = 0; constexpr int KERNING = 0;
const int W = pause_font_->length(TEXT, KERNING); const int W = pause_font_->length(text, KERNING);
const int OFF_LEFT = -W; const int OFF_LEFT = -W;
int x = GameCanvas::CENTER_X; int x = GameCanvas::CENTER_X;
if (transition_.phase == 2) { if (transition_.phase == phase_enter) {
const int DUR = transition_config_.intro_text_enter_ms; const float T = (dur_enter > 0)
const float T = (DUR > 0) ? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_enter), 0.0F, 1.0F)
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(DUR), 0.0F, 1.0F)
: 1.0F; : 1.0F;
const auto CURVE = (transition_config_.text_enter_curve != nullptr) ? transition_config_.text_enter_curve : &Easing::linear; const auto CURVE = (transition_config_.text_enter_curve != nullptr) ? transition_config_.text_enter_curve : &Easing::linear;
x = Easing::lerpInt(OFF_LEFT, GameCanvas::CENTER_X, CURVE(T)); x = Easing::lerpInt(OFF_LEFT, GameCanvas::CENTER_X, CURVE(T));
} else if (transition_.phase == 4) { } else if (transition_.phase == phase_exit) {
const int DUR = transition_config_.intro_text_exit_ms; const float T = (dur_exit > 0)
const float T = (DUR > 0) ? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_exit), 0.0F, 1.0F)
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(DUR), 0.0F, 1.0F)
: 1.0F; : 1.0F;
const auto CURVE = (transition_config_.text_exit_curve != nullptr) ? transition_config_.text_exit_curve : &Easing::linear; const auto CURVE = (transition_config_.text_exit_curve != nullptr) ? transition_config_.text_exit_curve : &Easing::linear;
x = Easing::lerpInt(GameCanvas::CENTER_X, OFF_LEFT, CURVE(T)); x = Easing::lerpInt(GameCanvas::CENTER_X, OFF_LEFT, CURVE(T));
@@ -599,7 +644,7 @@ void GameScene::renderRoomText() {
pause_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG, pause_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG,
x, x,
Y, Y,
TEXT, text,
KERNING, KERNING,
UiConfig::get().notificationText(), UiConfig::get().notificationText(),
STROKE_DIST, STROKE_DIST,