Skip to content

字模也是资产

老雷管字体叫字模——活字印刷里刻着字形的那套模具。这个叫法意外地贴切:字体文件(.ttf/.otf)就是一整套字形轮廓的集合,引擎拿它当资产管理,和第 14 章的图片、剧本一个待遇——库房上架,提货单取用。

rust
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 拿提货单,TextFontfont 字段挂上它(examples/listing-16-03.rs)

console
cargo run -p ch16-text --example listing-16-03
text
老雷:字模进库房了,跟道具一个走法——先开提货单。

中文台词以 48 号字清晰显示在窗口中央,下方一行小的英文仍用默认等宽字体

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 到底是什么?