字模也是资产
老雷管字体叫字模——活字印刷里刻着字形的那套模具。这个叫法意外地贴切:字体文件(.ttf/.otf)就是一整套字形轮廓的集合,引擎拿它当资产管理,和第 14 章的图片、剧本一个待遇——库房上架,提货单取用。
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
// 字体也是资产:load 拿到一张 Handle<Font> 提货单
let zh_font = asset_server.load("fonts/book-sans-sc-regular.otf");
commands.spawn((
Text2d::new("夜渡无人,秋水自横。"),
TextFont {
font: FontSource::Handle(zh_font),
font_size: FontSize::Px(48.0),
..default()
},
));
// 对照行:不指定 TextFont 就用内置默认字体(只有 95 个 ASCII 字形)
commands.spawn((
Text2d::new("THE NIGHT FERRY"),
Transform::from_xyz(0.0, -80.0, 0.0),
));
println!("老雷:字模进库房了,跟道具一个走法——先开提货单。");
}Listing 16-3:asset_server.load 拿提货单,TextFont 的 font 字段挂上它(examples/listing-16-03.rs)
cargo run -p ch16-text --example listing-16-03老雷:字模进库房了,跟道具一个走法——先开提货单。
Figure 16-3:词上屏了。中文行用加载的字体,英文对照行仍是默认字模
熟面孔到齐:load("fonts/book-sans-sc-regular.otf") 返回 Handle<Font>,Font 是 bevy_text 的字体资产类型。和第 14 章的规矩完全一致——load 立刻返回、后台慢慢读盘,字体没到货的那几帧,这段文字先不上屏,到货后自动补排。窗口刚打开时字幕“闪现”一下,就是这个异步到货的瞬间。
TextFont 是文字的“铸字规格单”,它管四件事:font(哪副字模)、font_size(多大字号,默认 20)、font_smoothing(磨不磨边,16.3 节)和 weight(字重——只对可变字重字体有效,普通字体文件一个文件就是一个字重,要粗体就换 Bold 字体文件,16.6 节的做法)。
几条用字模前该知道的细则:
- 格式只认
.ttf和.otf。Bevy 的字体加载器只注册了这两个扩展名。Windows 自带的中文字体很多是.ttc(TrueType Collection,多副字体打包),直接拿来加载会以“找不到对应加载器”的资产错误收场。 - 按路径取货,不问字体的内部名字。
load的参数是文件路径,字体文件里写的家族名(“Noto Sans SC”之类)在加载这一步完全不参与。文件叫什么名都行——引擎不会“按字体名找字体”。 - 默认字模也走同一套机制。
TextFont不指定font时,用的是Handle::<Font>::default()——Bevy 启动时把内置的 Fira Mono 子集插在了这个默认提货单上。所有不指定字体的文字共用它。
这副字模从哪来
库房里这两个 book-sans-sc-*.otf 不是凭空出现的,来历值得交代——这是中文游戏开发绕不开的一道工序。
直接可用的选择是开源中文字体,常见的有思源黑体(Source Han Sans / Noto Sans SC)、霞鹜文楷等,都以 SIL OFL 许可证发布——允许随游戏免费分发。但原始文件不小:一副完整的简体中文字体约 8~17 MB,因为它装着两万多个汉字的轮廓。游戏里实际用到的字往往只是一小部分,所以行业惯例是子集化(subsetting):用工具把用得到的字符裁出来,做成一副小字体。
本书的做法在 scripts/make_ch16_assets.py 里:下载 Noto Sans SC 的 Regular 与 Bold,用 fontTools 裁出 GB2312 字符集(6763 个常用汉字加符号,足够日常文本),再并入本章源码里实际出现的所有字符兜底——成品每副约 1.9 MB。脚本还把字体改名成了“Book Sans SC”:OFL 许可证有保留字体名条款,修改过的字体不得再用原名分发,子集化属于修改。
子集化的代价要心里有数:裁掉的字真的没了。本章撞见过一次——台词里的“欸乃”(橹声)用到的“欸”字不在 GB2312 里,第一版子集就漏了它,上屏静默空了一格。所以子集字符表要么覆盖面留足余量,要么像本书脚本一样自动扫描源码用字。玩家能输入任意文字的场合(聊天框、起名),子集化要格外保守。
借来的字形
还有一个值得知道的行为。把 Listing 16-3 的英文对照行换成中文、但不给它指定字体(仍用默认字模)——按上一节的经验该出豆腐块,实际却正常显示了。
原因:排版引擎在指定字体里查不到字形时,会在所有登记在册的字体里再找一轮——同一程序里第一行字正用着中文字体,它的字形就这么被“借”了过去。听起来很贴心,但别把它当机制依赖:借到哪副字体不由你挑(程序里有多副字体时结果难料),而且一旦没有任何文本用着那副字体,无字可借,豆腐块就回来了。正路始终是:每段文字明确指定自己的字体。
字模的事齐了。下一节回答一个看起来理所当然、实际全是误会的问题:font_size 到底是什么?