refactor(game-scene): extreu helpers de renderRoomText i elimina el NOLINT
This commit is contained in:
1 parent
8243e95ef1
commit
99eb42b319
2 files changed
+142
-130
No files matched your search
+130
-130
@@ -640,165 +640,165 @@ void GameScene::renderRoomFade() {
|
||||
Screen::get().blendRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, 0x00000000U, ALPHA);
|
||||
}
|
||||
|
||||
// TEMPORAL — al pròxim commit s'extrauen compute_x, compute_panel_height,
|
||||
// draw_panel i draw_line a mètodes privats; aquest NOLINT és només per
|
||||
// "guardar feina" mentre encara estem fent ajustos visuals.
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void GameScene::renderRoomText() {
|
||||
// Text de la transició de sala. Font dedicada (`room_text_font_`,
|
||||
// CaesarDressing per defecte) — la pausa té la seua i no es barregen.
|
||||
// Patró esquerra → centre → dreta amb les durades pròpies de la
|
||||
// transició.
|
||||
// Orquestrador del rètol de transició de sala. La feina pesada
|
||||
// (animació de X, animació d'altura del panel i pintat) la fan
|
||||
// quatre helpers privats; aquesta funció només decideix quin
|
||||
// text dibuixar i amb quines fases segons l'estat.
|
||||
//
|
||||
// Mapatge per estat (només els estats amb text actiu hi entren):
|
||||
// ROOM_INTRO: phases 2..4 → enter / hold / exit; dues línies
|
||||
// (piràmide damunt, sala davall) que comparteixen
|
||||
// animació però es dibuixen amb writeDX independents
|
||||
// per deixar la porta oberta a desacoblar-les.
|
||||
// (piràmide damunt, sala davall) que travessen la
|
||||
// pantalla en direccions oposades.
|
||||
// ROOM_OUTRO: phases 0..2 → enter / hold / exit; dues línies
|
||||
// oposades ("fase" / "completada"). Phase 3 és el
|
||||
// fade-out i 4 la pausa en negre — cap text en cap
|
||||
// de les dues.
|
||||
if (!room_text_font_) { return; }
|
||||
|
||||
constexpr int KERNING = 0;
|
||||
// Centre vertical de la zona de joc (canvas sense la HUD). Reemplaça
|
||||
// l'antic GameCanvas::CENTER_Y per descomptar la franja del marcador
|
||||
// i tenir el rètol equilibrat sobre el playfield.
|
||||
const int PLAY_CENTER_Y = (GameCanvas::HEIGHT + Map::hud_h) / 2;
|
||||
|
||||
constexpr int KERNING = 0;
|
||||
constexpr Uint8 STROKE_DIST = 1;
|
||||
// Colors del rètol via UiConfig — abans el contorn estava
|
||||
// hardcodejat a 255. Ara `data/ui.yaml > colors.room_text` i
|
||||
// `room_text_outline` controlen relleno i contorn sense afectar
|
||||
// les notificacions del HUD.
|
||||
const Uint8 FILL_COLOR = UiConfig::get().roomText();
|
||||
const Uint8 OUTLINE_COLOR = UiConfig::get().roomTextOutline();
|
||||
|
||||
// Calcula la X animada d'una línia: entra des de fora de pantalla,
|
||||
// s'atura al CENTER_X durant el hold i surt pel costat oposat.
|
||||
// `from_right=false` és el patró clàssic (entra per l'esquerra,
|
||||
// surt cap a la dreta); `true` el flipa (entra per la dreta, surt
|
||||
// cap a l'esquerra). Així les dues línies de l'INTRO poden
|
||||
// travessar la pantalla en direccions oposades.
|
||||
auto compute_x = [&](int width, int phase_enter, int phase_exit, int dur_enter, int dur_exit, bool from_right) -> int {
|
||||
const int OFF_LEFT = -width;
|
||||
const int OFF_RIGHT = GameCanvas::WIDTH;
|
||||
const int OFF_ENTER = from_right ? OFF_RIGHT : OFF_LEFT;
|
||||
const int OFF_EXIT = from_right ? OFF_LEFT : OFF_RIGHT;
|
||||
if (transition_.phase == phase_enter) {
|
||||
const float T = (dur_enter > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_enter), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_enter_curve != nullptr) ? transition_config_.text_enter_curve : &Easing::linear;
|
||||
return Easing::lerpInt(OFF_ENTER, GameCanvas::CENTER_X, CURVE(T));
|
||||
}
|
||||
if (transition_.phase == phase_exit) {
|
||||
const float T = (dur_exit > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_exit), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_exit_curve != nullptr) ? transition_config_.text_exit_curve : &Easing::linear;
|
||||
return Easing::lerpInt(GameCanvas::CENTER_X, OFF_EXIT, CURVE(T));
|
||||
}
|
||||
return GameCanvas::CENTER_X;
|
||||
};
|
||||
|
||||
// Panel translúcid sota el rètol: l'altura s'anima de 0 a
|
||||
// `text_panel_height_px` durant l'enter (amb la mateixa
|
||||
// text_enter_curve que els textos), es manté plena durant el hold
|
||||
// i decreix durant l'exit. Cada estat (INTRO, OUTRO) li passa la
|
||||
// tripleta de fases (enter, hold, exit) i les seues durades.
|
||||
auto compute_panel_height = [&](int p_enter, int p_hold, int p_exit, int d_enter, int d_exit) -> int {
|
||||
const int MAX = transition_config_.text_panel_height_px;
|
||||
if (transition_.phase == p_enter) {
|
||||
const float T = (d_enter > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(d_enter), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_enter_curve != nullptr) ? transition_config_.text_enter_curve : &Easing::linear;
|
||||
return Easing::lerpInt(0, MAX, CURVE(T));
|
||||
}
|
||||
if (transition_.phase == p_hold) { return MAX; }
|
||||
if (transition_.phase == p_exit) {
|
||||
const float T = (d_exit > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(d_exit), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_exit_curve != nullptr) ? transition_config_.text_exit_curve : &Easing::linear;
|
||||
return Easing::lerpInt(MAX, 0, CURVE(T));
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto draw_panel = [&](int panel_height) {
|
||||
if (panel_height <= 0 || transition_config_.text_panel_alpha == 0) { return; }
|
||||
Screen::get().blendRect(0,
|
||||
PLAY_CENTER_Y - (panel_height / 2),
|
||||
GameCanvas::WIDTH,
|
||||
panel_height,
|
||||
transition_config_.text_panel_color,
|
||||
transition_config_.text_panel_alpha);
|
||||
};
|
||||
|
||||
auto draw_line = [&](const std::string& text, int x, int y) {
|
||||
room_text_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG,
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
KERNING,
|
||||
FILL_COLOR,
|
||||
STROKE_DIST,
|
||||
OUTLINE_COLOR);
|
||||
};
|
||||
// Colors del rètol via UiConfig (`data/ui.yaml > colors.room_text` i
|
||||
// `room_text_outline`). Independents dels colors de notificació.
|
||||
const Uint8 FILL = UiConfig::get().roomText();
|
||||
const Uint8 OUTLINE = UiConfig::get().roomTextOutline();
|
||||
const int LINE_HEIGHT = room_text_font_->getLineHeight();
|
||||
|
||||
if (state_ == RoomFsm::State::ROOM_INTRO && transition_.phase >= 2 && transition_.phase <= 4) {
|
||||
const int DUR_ENTER = transition_config_.intro_text_enter_ms;
|
||||
const int DUR_EXIT = transition_config_.intro_text_exit_ms;
|
||||
// Panel primer (queda sota el text gràcies a l'ordre de blits).
|
||||
draw_panel(compute_panel_height(2, 3, 4, transition_config_.intro_text_enter_ms, transition_config_.intro_text_exit_ms));
|
||||
drawRoomTextPanel(PLAY_CENTER_Y,
|
||||
computeRoomTextPanelHeight(2, 3, 4, DUR_ENTER, DUR_EXIT));
|
||||
|
||||
const auto& mgr = Levels::LevelManager::get();
|
||||
const std::string LINE_PYRAMID = RoomFsm::formatIntroPyramidLine(mgr.currentPyramid());
|
||||
const std::string LINE_ROOM = RoomFsm::formatIntroRoomLine(mgr.currentRoom());
|
||||
const int W_PYRAMID = room_text_font_->length(LINE_PYRAMID, KERNING);
|
||||
const int W_ROOM = room_text_font_->length(LINE_ROOM, KERNING);
|
||||
const int DUR_ENTER = transition_config_.intro_text_enter_ms;
|
||||
const int DUR_EXIT = transition_config_.intro_text_exit_ms;
|
||||
// Direccions oposades: la línia de la piràmide entra per
|
||||
// l'esquerra, la de la sala per la dreta. Quan surten, cada
|
||||
// una continua cap al costat oposat al que ha entrat.
|
||||
const int X_PYRAMID = compute_x(W_PYRAMID, 2, 4, DUR_ENTER, DUR_EXIT, /*from_right=*/false);
|
||||
const int X_ROOM = compute_x(W_ROOM, 2, 4, DUR_ENTER, DUR_EXIT, /*from_right=*/true);
|
||||
// Bloc de dues línies centrat al voltant de CENTER_Y. Entre
|
||||
// les dues línies hi posem `intro_text_line_gap_px` extra (a
|
||||
// més del lineHeight natural) per donar-los aire visual; la
|
||||
// meitat la traiem cap amunt de la piràmide i la meitat la
|
||||
// sumem cap avall a la sala, mantenint el centre del bloc fix.
|
||||
const int LINE_HEIGHT = room_text_font_->getLineHeight();
|
||||
// Direccions oposades: piràmide entra per l'esquerra i surt per
|
||||
// la dreta; sala fa el contrari.
|
||||
const int X_PYRAMID = computeRoomTextX(room_text_font_->length(LINE_PYRAMID, KERNING),
|
||||
2,
|
||||
4,
|
||||
DUR_ENTER,
|
||||
DUR_EXIT,
|
||||
/*from_right=*/false);
|
||||
const int X_ROOM = computeRoomTextX(room_text_font_->length(LINE_ROOM, KERNING),
|
||||
2,
|
||||
4,
|
||||
DUR_ENTER,
|
||||
DUR_EXIT,
|
||||
/*from_right=*/true);
|
||||
// Bloc de dues línies centrat al voltant del PLAY_CENTER_Y. El
|
||||
// gap (`intro_text_line_gap_px`) es reparteix simètricament:
|
||||
// mig amunt a la piràmide, mig avall a la sala.
|
||||
const int GAP = transition_config_.intro_text_line_gap_px;
|
||||
const int Y_PYRAMID = PLAY_CENTER_Y - LINE_HEIGHT - (GAP / 2);
|
||||
const int Y_ROOM = PLAY_CENTER_Y + (GAP / 2);
|
||||
draw_line(LINE_PYRAMID, X_PYRAMID, Y_PYRAMID);
|
||||
draw_line(LINE_ROOM, X_ROOM, Y_ROOM);
|
||||
drawRoomTextLine(LINE_PYRAMID, X_PYRAMID, PLAY_CENTER_Y - LINE_HEIGHT - (GAP / 2), FILL, OUTLINE);
|
||||
drawRoomTextLine(LINE_ROOM, X_ROOM, PLAY_CENTER_Y + (GAP / 2), FILL, OUTLINE);
|
||||
} else if (state_ == RoomFsm::State::ROOM_OUTRO && transition_.phase >= 0 && transition_.phase <= 2) {
|
||||
draw_panel(compute_panel_height(0, 1, 2, transition_config_.outro_text_enter_ms, transition_config_.outro_text_exit_ms));
|
||||
|
||||
// Mateix patró que l'INTRO: dues línies en direccions oposades.
|
||||
// Línia 1 ("Fase") per l'esquerra, línia 2 ("Completada") per
|
||||
// la dreta; surten cap al costat oposat del que han entrat.
|
||||
const std::string LINE_1 = Locale::get().text("game.room_complete_line1");
|
||||
const std::string LINE_2 = Locale::get().text("game.room_complete_line2");
|
||||
const int W_1 = room_text_font_->length(LINE_1, KERNING);
|
||||
const int W_2 = room_text_font_->length(LINE_2, KERNING);
|
||||
const int DUR_ENTER = transition_config_.outro_text_enter_ms;
|
||||
const int DUR_EXIT = transition_config_.outro_text_exit_ms;
|
||||
const int X_1 = compute_x(W_1, 0, 2, DUR_ENTER, DUR_EXIT, /*from_right=*/false);
|
||||
const int X_2 = compute_x(W_2, 0, 2, DUR_ENTER, DUR_EXIT, /*from_right=*/true);
|
||||
const int LINE_HEIGHT = room_text_font_->getLineHeight();
|
||||
drawRoomTextPanel(PLAY_CENTER_Y,
|
||||
computeRoomTextPanelHeight(0, 1, 2, DUR_ENTER, DUR_EXIT));
|
||||
|
||||
const std::string LINE_1 = Locale::get().text("game.room_complete_line1");
|
||||
const std::string LINE_2 = Locale::get().text("game.room_complete_line2");
|
||||
const int X_1 = computeRoomTextX(room_text_font_->length(LINE_1, KERNING),
|
||||
0,
|
||||
2,
|
||||
DUR_ENTER,
|
||||
DUR_EXIT,
|
||||
/*from_right=*/false);
|
||||
const int X_2 = computeRoomTextX(room_text_font_->length(LINE_2, KERNING),
|
||||
0,
|
||||
2,
|
||||
DUR_ENTER,
|
||||
DUR_EXIT,
|
||||
/*from_right=*/true);
|
||||
const int GAP = transition_config_.outro_text_line_gap_px;
|
||||
const int Y_1 = PLAY_CENTER_Y - LINE_HEIGHT - (GAP / 2);
|
||||
const int Y_2 = PLAY_CENTER_Y + (GAP / 2);
|
||||
draw_line(LINE_1, X_1, Y_1);
|
||||
draw_line(LINE_2, X_2, Y_2);
|
||||
drawRoomTextLine(LINE_1, X_1, PLAY_CENTER_Y - LINE_HEIGHT - (GAP / 2), FILL, OUTLINE);
|
||||
drawRoomTextLine(LINE_2, X_2, PLAY_CENTER_Y + (GAP / 2), FILL, OUTLINE);
|
||||
}
|
||||
}
|
||||
|
||||
auto GameScene::computeRoomTextX(int width, int phase_enter, int phase_exit, int dur_enter, int dur_exit, bool from_right) const -> int {
|
||||
// X animada d'una línia: entra des de fora de pantalla, s'atura al
|
||||
// CENTER_X durant el hold (fora del rang d'aquesta funció — el caller
|
||||
// garanteix que cau a la branca return final) i surt pel costat
|
||||
// oposat. `from_right=false` és el patró clàssic (esquerra → dreta);
|
||||
// `true` el flipa.
|
||||
const int OFF_LEFT = -width;
|
||||
const int OFF_RIGHT = GameCanvas::WIDTH;
|
||||
const int OFF_ENTER = from_right ? OFF_RIGHT : OFF_LEFT;
|
||||
const int OFF_EXIT = from_right ? OFF_LEFT : OFF_RIGHT;
|
||||
if (transition_.phase == phase_enter) {
|
||||
const float T = (dur_enter > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_enter), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_enter_curve != nullptr) ? transition_config_.text_enter_curve : &Easing::linear;
|
||||
return Easing::lerpInt(OFF_ENTER, GameCanvas::CENTER_X, CURVE(T));
|
||||
}
|
||||
if (transition_.phase == phase_exit) {
|
||||
const float T = (dur_exit > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_exit), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_exit_curve != nullptr) ? transition_config_.text_exit_curve : &Easing::linear;
|
||||
return Easing::lerpInt(GameCanvas::CENTER_X, OFF_EXIT, CURVE(T));
|
||||
}
|
||||
return GameCanvas::CENTER_X;
|
||||
}
|
||||
|
||||
auto GameScene::computeRoomTextPanelHeight(int phase_enter, int phase_hold, int phase_exit, int dur_enter, int dur_exit) const -> int {
|
||||
// Altura animada del panel translúcid sota el rètol. Creix de 0 a
|
||||
// `text_panel_height_px` amb `text_enter_curve` durant l'enter, es
|
||||
// manté plena durant el hold i decreix amb `text_exit_curve`
|
||||
// durant l'exit. Fora d'aquestes tres fases retorna 0.
|
||||
const int MAX = transition_config_.text_panel_height_px;
|
||||
if (transition_.phase == phase_enter) {
|
||||
const float T = (dur_enter > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_enter), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_enter_curve != nullptr) ? transition_config_.text_enter_curve : &Easing::linear;
|
||||
return Easing::lerpInt(0, MAX, CURVE(T));
|
||||
}
|
||||
if (transition_.phase == phase_hold) { return MAX; }
|
||||
if (transition_.phase == phase_exit) {
|
||||
const float T = (dur_exit > 0)
|
||||
? std::clamp(static_cast<float>(transition_.elapsed_ms) / static_cast<float>(dur_exit), 0.0F, 1.0F)
|
||||
: 1.0F;
|
||||
const auto CURVE = (transition_config_.text_exit_curve != nullptr) ? transition_config_.text_exit_curve : &Easing::linear;
|
||||
return Easing::lerpInt(MAX, 0, CURVE(T));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GameScene::drawRoomTextPanel(int center_y, int panel_height) const {
|
||||
// No-op si l'altura és nul·la o l'alpha està a zero — així evitem
|
||||
// un blendRect inútil i deixem el caller cridar incondicionalment.
|
||||
if (panel_height <= 0 || transition_config_.text_panel_alpha == 0) { return; }
|
||||
Screen::get().blendRect(0,
|
||||
center_y - (panel_height / 2),
|
||||
GameCanvas::WIDTH,
|
||||
panel_height,
|
||||
transition_config_.text_panel_color,
|
||||
transition_config_.text_panel_alpha);
|
||||
}
|
||||
|
||||
void GameScene::drawRoomTextLine(const std::string& text, int x, int y, std::uint8_t fill_color, std::uint8_t outline_color) const {
|
||||
constexpr int KERNING = 0;
|
||||
constexpr Uint8 STROKE_DIST = 1;
|
||||
room_text_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG,
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
KERNING,
|
||||
fill_color,
|
||||
STROKE_DIST,
|
||||
outline_color);
|
||||
}
|
||||
|
||||
void GameScene::spawnDust(float feet_x_center, float feet_y, bool is_landing) {
|
||||
// Mida del sprite de Dust llegida del seu YAML. Hardcoded per
|
||||
// simplificar — si canvia, només cal tocar dust.yaml + aquesta
|
||||
|
||||
@@ -196,6 +196,18 @@ class GameScene : public Scene {
|
||||
void renderRoomFade();
|
||||
void renderRoomText();
|
||||
|
||||
// Helpers de `renderRoomText`. Tots consulten `transition_` i
|
||||
// `transition_config_` directament a través de `this` — així les
|
||||
// signatures es queden netes i la funció pública s'orquestra amb
|
||||
// poques línies. Tres tasques separades:
|
||||
// - X animada d'una línia (entra per un costat, surt per l'altre).
|
||||
// - Altura animada del panel translúcid (creix, manté, decreix).
|
||||
// - Pintat efectiu del panel i d'una línia de text.
|
||||
[[nodiscard]] auto computeRoomTextX(int width, int phase_enter, int phase_exit, int dur_enter, int dur_exit, bool from_right) const -> int;
|
||||
[[nodiscard]] auto computeRoomTextPanelHeight(int phase_enter, int phase_hold, int phase_exit, int dur_enter, int dur_exit) const -> int;
|
||||
void drawRoomTextPanel(int center_y, int panel_height) const;
|
||||
void drawRoomTextLine(const std::string& text, int x, int y, std::uint8_t fill_color, std::uint8_t outline_color) const;
|
||||
|
||||
// Posició inicial dels jugadors derivada de `Map::door_col`. Tots
|
||||
// els jugadors comparteixen Y (centre del corredor exterior superior);
|
||||
// X depèn del nombre actiu: amb un jugador queda centrat sota la
|
||||
|
||||
Reference in new issue
Block a user