feat(intro): font dedicada (CaesarDressing) i separació vertical entre línies

Nova font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO):
CaesarDressing-Regular, vista com a fonts.room_text al data/ui.yaml i
exposada per UiConfig::roomTextFont(). Director la preloda al costat
de la del HUD i la del placeholder; GameScene construeix
room_text_font_ paral·lel a pause_font_ i renderRoomText l'usa per a
les dues línies de l'INTRO i el text de l'OUTRO. La pausa segueix
amb la font placeholder antiga — no es barregen.

Espai vertical configurable: nova clau room_intro.line_gap_px al
data/scenes/game.yaml (default 8 px). El gap es suma al lineHeight
natural del font i es reparteix simètricament cap amunt i cap avall
del CENTER_Y, deixant el bloc de dues línies sempre centrat. Es
poden afinar des del YAML sense recompilar.
This commit is contained in:
JailDesigner committed 2026-04-26 15:40:09 +02:00
1 parent f52e7f4682
commit af25a31639
11 files changed
+201 -17

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
# Font: CaesarDressing-Regular — generado desde CaesarDressing-Regular.ttf size 24
# Generado con font_gen.py
box_width 18
box_height 30
columns 15
# codepoint_decimal ancho_visual
32 8 # U+0020
33 5 # !
34 6 # "
35 14 # #
36 7 # $
37 17 # %
38 9 # &
39 3 # '
40 10 # (
41 8 # )
42 10 # *
43 11 # +
44 5 # ,
45 10 # -
46 3 # .
47 9 # /
48 12 # 0
49 6 # 1
50 10 # 2
51 9 # 3
52 12 # 4
53 11 # 5
54 13 # 6
55 11 # 7
56 13 # 8
57 13 # 9
58 5 # :
59 4 # ;
60 11 # <
61 11 # =
62 11 # >
63 11 # ?
64 14 # @
65 14 # A
66 12 # B
67 11 # C
68 12 # D
69 10 # E
70 10 # F
71 15 # G
72 12 # H
73 9 # I
74 9 # J
75 13 # K
76 10 # L
77 18 # M
78 12 # N
79 13 # O
80 12 # P
81 12 # Q
82 13 # R
83 9 # S
84 12 # T
85 11 # U
86 12 # V
87 17 # W
88 11 # X
89 12 # Y
90 10 # Z
91 8 # [
92 8 # \
93 7 # ]
94 11 # ^
95 13 # _
96 7 # `
97 13 # a
98 12 # b
99 11 # c
100 12 # d
101 10 # e
102 10 # f
103 15 # g
104 12 # h
105 5 # i
106 9 # j
107 13 # k
108 10 # l
109 18 # m
110 12 # n
111 13 # o
112 12 # p
113 12 # q
114 12 # r
115 10 # s
116 12 # t
117 11 # u
118 12 # v
119 17 # w
120 11 # x
121 12 # y
122 10 # z
123 9 # {
124 5 # |
125 9 # }
126 14 # ~
192 14 # À
193 14 # Á
200 10 # È
201 10 # É
204 9 # Ì
205 9 # Í
210 13 # Ò
211 13 # Ó
217 11 # Ù
218 11 # Ú
209 12 # Ñ
199 11 # Ç
224 13 # à
225 13 # á
232 10 # è
233 10 # é
236 7 # ì
237 8 # í
242 13 # ò
243 13 # ó
249 11 # ù
250 11 # ú
241 12 # ñ
231 11 # ç
161 5 # ¡
191 10 # ¿
171 11 # «
187 11 # »
183 3 # ·
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

+7 -1
View File
@@ -36,6 +36,12 @@ fonts:
gif: "data/fonts/ari-w9500-condensed-display.gif" gif: "data/fonts/ari-w9500-condensed-display.gif"
fnt: "data/fonts/ari-w9500-condensed-display.fnt" fnt: "data/fonts/ari-w9500-condensed-display.fnt"
kerning: 1 kerning: 1
# Font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO).
room_text:
name: "CaesarDressing-Regular"
gif: "data/fonts/CaesarDressing-Regular.gif"
fnt: "data/fonts/CaesarDressing-Regular.fnt"
kerning: 1
menu_title: menu_title:
name: "ari-w9500-display" name: "ari-w9500-display"
gif: "data/fonts/ari-w9500-display.gif" gif: "data/fonts/ari-w9500-display.gif"
@@ -48,7 +54,7 @@ fonts:
kerning: 0 kerning: 0
hud: hud:
bg_enabled: true bg_enabled: false
entry_ms: 800 # durada de l'animació d'entrada dels tres blocs entry_ms: 800 # durada de l'animació d'entrada dels tres blocs
flash_ms: 250 # durada del flash quan un jugador rep un hit flash_ms: 250 # durada del flash quan un jugador rep un hit
shake_ms: 300 # durada del shake que acompanya el flash shake_ms: 300 # durada del shake que acompanya el flash
+8
View File
@@ -122,6 +122,14 @@ Director::Director() {
const std::string PLACEHOLDER_FONT_GIF_KEY = UiConfig::get().placeholderFont().name + ".gif"; const std::string PLACEHOLDER_FONT_GIF_KEY = UiConfig::get().placeholderFont().name + ".gif";
Resource::Cache::get().loadSurface(PLACEHOLDER_FONT_GIF_KEY, UiConfig::get().placeholderFont().gif); Resource::Cache::get().loadSurface(PLACEHOLDER_FONT_GIF_KEY, UiConfig::get().placeholderFont().gif);
Resource::Cache::get().getSurface(PLACEHOLDER_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui")); Resource::Cache::get().getSurface(PLACEHOLDER_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
// Font del rètol de transició de sala (la consumeix `GameScene`
// tant per la línia "Piràmide N / Sala M" de l'INTRO com pel
// "Fase completada" de l'OUTRO). Es preloda ací per consistència
// amb la resta de fonts d'UI; sense això, la primera entrada a
// GAME aniria a disc.
const std::string ROOM_TEXT_FONT_GIF_KEY = UiConfig::get().roomTextFont().name + ".gif";
Resource::Cache::get().loadSurface(ROOM_TEXT_FONT_GIF_KEY, UiConfig::get().roomTextFont().gif);
Resource::Cache::get().getSurface(ROOM_TEXT_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
// Música de la piràmide 1 (primer i únic track de moment). Carreguem ací // Música de la piràmide 1 (primer i únic track de moment). Carreguem ací
// perquè forma part del preload d'assets, com la font i la paleta. // perquè forma part del preload d'assets, com la font i la paleta.
+2
View File
@@ -96,6 +96,7 @@ UiConfig::UiConfig()
pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)), pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)),
hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING}, hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING},
placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING}, placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING},
room_text_font_{.name = Defaults::UI::Font::ROOM_TEXT_NAME, .gif = Defaults::UI::Font::ROOM_TEXT_GIF, .fnt = Defaults::UI::Font::ROOM_TEXT_FNT, .kerning = Defaults::UI::Font::ROOM_TEXT_KERNING},
menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING}, menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING},
menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING} { menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING} {
} }
@@ -159,6 +160,7 @@ void UiConfig::load(const std::string& file_path) {
const auto& f = yaml["fonts"]; const auto& f = yaml["fonts"];
readFont(f, "hud", hud_font_); readFont(f, "hud", hud_font_);
readFont(f, "placeholder", placeholder_font_); readFont(f, "placeholder", placeholder_font_);
readFont(f, "room_text", room_text_font_);
readFont(f, "menu_title", menu_title_font_); readFont(f, "menu_title", menu_title_font_);
readFont(f, "menu_items", menu_items_font_); readFont(f, "menu_items", menu_items_font_);
} }
+2
View File
@@ -77,6 +77,7 @@ class UiConfig {
// Fonts de UI. // Fonts de UI.
[[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; } [[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; }
[[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; } [[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; }
[[nodiscard]] auto roomTextFont() const -> const Font& { return room_text_font_; }
[[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; } [[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; }
[[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; } [[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; }
@@ -118,6 +119,7 @@ class UiConfig {
Font hud_font_; Font hud_font_;
Font placeholder_font_; Font placeholder_font_;
Font room_text_font_;
Font menu_title_font_; Font menu_title_font_;
Font menu_items_font_; Font menu_items_font_;
+26 -16
View File
@@ -436,6 +436,13 @@ GameScene::GameScene(Config config)
pause_font_ = std::make_shared<Text>(pause_surface, UiConfig::get().placeholderFont().fnt); pause_font_ = std::make_shared<Text>(pause_surface, UiConfig::get().placeholderFont().fnt);
pause_font_->setDefaultKerning(UiConfig::get().placeholderFont().kerning); 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";
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_->setDefaultKerning(UiConfig::get().roomTextFont().kerning);
// Timings i corbes de la FSM de sala. Llegits del YAML una sola // Timings i corbes de la FSM de sala. Llegits del YAML una sola
// vegada; canvis requereixen reiniciar el joc. // vegada; canvis requereixen reiniciar el joc.
transition_config_ = RoomFsm::loadConfig(Defaults::GameScene::CONFIG_YAML); transition_config_ = RoomFsm::loadConfig(Defaults::GameScene::CONFIG_YAML);
@@ -627,10 +634,10 @@ void GameScene::renderRoomFade() {
} }
void GameScene::renderRoomText() { void GameScene::renderRoomText() {
// Text de la transició de sala. Reusa la font cachejada per a la // Text de la transició de sala. Font dedicada (`room_text_font_`,
// pausa (mateix tipus de lletra, mateix índex de color), i fa el // CaesarDressing per defecte) — la pausa té la seua i no es barregen.
// mateix patró esquerra → centre → dreta que el text de pausa però // Patró esquerra → centre → dreta amb les durades pròpies de la
// amb les durades pròpies de la transició. // transició.
// //
// Mapatge per estat (només els estats amb text actiu hi entren): // Mapatge per estat (només els estats amb text actiu hi entren):
// ROOM_INTRO: phases 2..4 → enter / hold / exit; dues línies // ROOM_INTRO: phases 2..4 → enter / hold / exit; dues línies
@@ -639,7 +646,7 @@ void GameScene::renderRoomText() {
// per deixar la porta oberta a desacoblar-les. // per deixar la porta oberta a desacoblar-les.
// ROOM_OUTRO: phases 0..2 → enter / hold / exit; una línia // ROOM_OUTRO: phases 0..2 → enter / hold / exit; una línia
// ("fase completada"). // ("fase completada").
if (!pause_font_) { return; } if (!room_text_font_) { return; }
constexpr int KERNING = 0; constexpr int KERNING = 0;
constexpr Uint8 OUTLINE_WHITE = 255; constexpr Uint8 OUTLINE_WHITE = 255;
@@ -671,7 +678,7 @@ void GameScene::renderRoomText() {
}; };
auto draw_line = [&](const std::string& text, int x, int y) { auto draw_line = [&](const std::string& text, int x, int y) {
pause_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG, room_text_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG,
x, x,
y, y,
text, text,
@@ -685,25 +692,28 @@ void GameScene::renderRoomText() {
const auto& mgr = Levels::LevelManager::get(); const auto& mgr = Levels::LevelManager::get();
const std::string LINE_PYRAMID = RoomFsm::formatIntroPyramidLine(mgr.currentPyramid()); const std::string LINE_PYRAMID = RoomFsm::formatIntroPyramidLine(mgr.currentPyramid());
const std::string LINE_ROOM = RoomFsm::formatIntroRoomLine(mgr.currentRoom()); const std::string LINE_ROOM = RoomFsm::formatIntroRoomLine(mgr.currentRoom());
const int W_PYRAMID = pause_font_->length(LINE_PYRAMID, KERNING); const int W_PYRAMID = room_text_font_->length(LINE_PYRAMID, KERNING);
const int W_ROOM = pause_font_->length(LINE_ROOM, 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_ENTER = transition_config_.intro_text_enter_ms;
const int DUR_EXIT = transition_config_.intro_text_exit_ms; const int DUR_EXIT = transition_config_.intro_text_exit_ms;
const int X_PYRAMID = compute_x(W_PYRAMID, 2, 4, DUR_ENTER, DUR_EXIT); const int X_PYRAMID = compute_x(W_PYRAMID, 2, 4, DUR_ENTER, DUR_EXIT);
const int X_ROOM = compute_x(W_ROOM, 2, 4, DUR_ENTER, DUR_EXIT); const int X_ROOM = compute_x(W_ROOM, 2, 4, DUR_ENTER, DUR_EXIT);
// Bloc de dues línies centrat al voltant de CENTER_Y: la // Bloc de dues línies centrat al voltant de CENTER_Y. Entre
// piràmide ocupa la línia de damunt, la sala la de davall. Si // les dues línies hi posem `intro_text_line_gap_px` extra (a
// un dia el font canvia d'alçada, `getLineHeight` ho reflecteix. // més del lineHeight natural) per donar-los aire visual; la
const int LINE_HEIGHT = pause_font_->getLineHeight(); // meitat la traiem cap amunt de la piràmide i la meitat la
const int Y_PYRAMID = GameCanvas::CENTER_Y - LINE_HEIGHT; // sumem cap avall a la sala, mantenint el centre del bloc fix.
const int Y_ROOM = GameCanvas::CENTER_Y; const int LINE_HEIGHT = room_text_font_->getLineHeight();
const int GAP = transition_config_.intro_text_line_gap_px;
const int Y_PYRAMID = GameCanvas::CENTER_Y - LINE_HEIGHT - (GAP / 2);
const int Y_ROOM = GameCanvas::CENTER_Y + (GAP / 2);
draw_line(LINE_PYRAMID, X_PYRAMID, Y_PYRAMID); draw_line(LINE_PYRAMID, X_PYRAMID, Y_PYRAMID);
draw_line(LINE_ROOM, X_ROOM, Y_ROOM); draw_line(LINE_ROOM, X_ROOM, Y_ROOM);
} else if (state_ == RoomFsm::State::ROOM_OUTRO && transition_.phase >= 0 && transition_.phase <= 2) { } else if (state_ == RoomFsm::State::ROOM_OUTRO && transition_.phase >= 0 && transition_.phase <= 2) {
const std::string TEXT = Locale::get().text("game.room_complete"); const std::string TEXT = Locale::get().text("game.room_complete");
const int W = pause_font_->length(TEXT, KERNING); const int W = room_text_font_->length(TEXT, KERNING);
const int X = compute_x(W, 0, 2, transition_config_.outro_text_enter_ms, transition_config_.outro_text_exit_ms); const int X = compute_x(W, 0, 2, transition_config_.outro_text_enter_ms, transition_config_.outro_text_exit_ms);
const int Y = GameCanvas::CENTER_Y - (pause_font_->getLineHeight() / 2); const int Y = GameCanvas::CENTER_Y - (room_text_font_->getLineHeight() / 2);
draw_line(TEXT, X, Y); draw_line(TEXT, X, Y);
} }
} }
+7
View File
@@ -153,6 +153,13 @@ class GameScene : public Scene {
// abans — ja cachejada al Director. // abans — ja cachejada al Director.
std::shared_ptr<Text> pause_font_; std::shared_ptr<Text> pause_font_;
// 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
// pròpia (CaesarDressing per defecte, configurable via
// `data/ui.yaml > fonts.room_text`). La pausa segueix amb la
// placeholder.
std::shared_ptr<Text> room_text_font_;
enum class PauseState : std::uint8_t { enum class PauseState : std::uint8_t {
IDLE, // Joc corrent; cap transició en marxa. scale == 1. IDLE, // Joc corrent; cap transició en marxa. scale == 1.
ENTERING, // Frenant cap a la pausa. scale decreix a 0. ENTERING, // Frenant cap a la pausa. scale decreix a 0.
+1
View File
@@ -43,6 +43,7 @@ namespace RoomFsm {
cfg.intro_text_hold_ms = readChildInt(n, "text_hold_ms", cfg.intro_text_hold_ms); cfg.intro_text_hold_ms = readChildInt(n, "text_hold_ms", cfg.intro_text_hold_ms);
cfg.intro_text_exit_ms = readChildInt(n, "text_exit_ms", cfg.intro_text_exit_ms); cfg.intro_text_exit_ms = readChildInt(n, "text_exit_ms", cfg.intro_text_exit_ms);
cfg.player_spawn_delay_ms = readChildInt(n, "player_spawn_delay_ms", cfg.player_spawn_delay_ms); cfg.player_spawn_delay_ms = readChildInt(n, "player_spawn_delay_ms", cfg.player_spawn_delay_ms);
cfg.intro_text_line_gap_px = readChildInt(n, "line_gap_px", cfg.intro_text_line_gap_px);
} }
if (yaml.contains("room_outro")) { if (yaml.contains("room_outro")) {
const auto& n = yaml["room_outro"]; const auto& n = yaml["room_outro"];
+7
View File
@@ -49,6 +49,13 @@ namespace RoomFsm {
// moment en què el fade-in acaba i el text encara no ha entrat. // moment en què el fade-in acaba i el text encara no ha entrat.
int player_spawn_delay_ms{500}; int player_spawn_delay_ms{500};
// Píxels extra entre les dues línies del text d'INTRO (piràmide
// damunt, sala davall). Es sumen al `lineHeight` natural del
// font per separar visualment les dues línies. 0 = stack
// immediat (com l'antic single-line). El YAML pot afinar-lo
// sense recompilar.
int intro_text_line_gap_px{8};
UiConfig::CurveFn text_enter_curve{nullptr}; UiConfig::CurveFn text_enter_curve{nullptr};
UiConfig::CurveFn text_exit_curve{nullptr}; UiConfig::CurveFn text_exit_curve{nullptr};
}; };
+9
View File
@@ -130,6 +130,15 @@ namespace Defaults {
constexpr const char* PLACEHOLDER_FNT = "data/fonts/ari-w9500-condensed-display.fnt"; constexpr const char* PLACEHOLDER_FNT = "data/fonts/ari-w9500-condensed-display.fnt";
constexpr int PLACEHOLDER_KERNING = 1; constexpr int PLACEHOLDER_KERNING = 1;
// Text de transició de sala (ROOM_INTRO "Piràmide N / Sala M" i
// ROOM_OUTRO "Fase completada"). Font temàtica diferenciada de la
// del HUD i de la dels placeholders perquè el rètol de fase tinga
// pes visual propi. La pausa segueix amb la PLACEHOLDER.
constexpr const char* ROOM_TEXT_NAME = "CaesarDressing-Regular";
constexpr const char* ROOM_TEXT_GIF = "data/fonts/CaesarDressing-Regular.gif";
constexpr const char* ROOM_TEXT_FNT = "data/fonts/CaesarDressing-Regular.fnt";
constexpr int ROOM_TEXT_KERNING = 1;
// Menú de servei (F9): títol en display normal, items en la // Menú de servei (F9): títol en display normal, items en la
// variant condensed-display (més compacta, perquè caben més // variant condensed-display (més compacta, perquè caben més
// files sense encongir la caixa). Kerning 0 a totes dues perquè // files sense encongir la caixa). Kerning 0 a totes dues perquè