Skip to content

自定义字体指南 ​

本文说明如何在 WasiCore GameUI 中让 Label、Input、TextButton 等文本控件使用自定义字体,以及字体不生效时该查什么。

核心结论 ​

Label.Font 设置的是字体 family,不是字体文件路径。字体 family 必须在地图加载时通过 ref/fontref.txt 预加载,否则运行期设置后会 fallback 到默认字体,看起来像“没有生效”。

csharp
#if CLIENT
var title = new Label
{
    Text = "自定义字体标题",
    Font = "font/my_title"u8,
    FontSize = 32,
};
#endif

上例中的字体文件应放在项目:

text
MyProject/
├── ref/
│   └── fontref.txt
└── res/
    └── font/
        └── my_title/
            └── my_title.ttf

ref/fontref.txt 写:

text
font/my_title

字体文件放在哪里 ​

用途文件位置family / 代码路径
项目私有随包字体<Project>/res/font/my_title/my_title.ttffont/my_title
公共 UI 字体或官方字体ui/font/Regular/Regular.otfui/font/Regular

项目 res/ 是资源根。字体放在 <Project>/res/font/... 时,代码和 fontref.txt 都从 res/ 下一级开始写,所以是 font/my_title,不是 res/font/my_title。

为了减少跨平台和打包后的大小写问题,自定义字体目录建议使用小写英文、数字和下划线;Label.Font 与 fontref.txt 使用同一个 family。

必须维护 ref/fontref.txt ​

ref/fontref.txt 是 GameUI 文本控件字体 family 的预加载列表。每行一个 family:

text
font/my_title
font/my_damage_number
ui/font/Regular
ui/font/NotoEmoji

推荐写 family 目录。旧逻辑兼容在 fontref.txt 中写具体字体文件,例如 font/my_title/my_title.ttf,运行时会取父目录作为 family 加载;但控件代码里仍应写 Font = "font/my_title"u8,不要写文件路径。

自动收集与手写维护的分工 ​

项目里的数编 UI(GameDataControl 及其派生控件)中设置的 Font,以及 GameCore.ResourceType.Font 类型资源,会在保存并生成时自动收集到当前地图的 ref/fontref.txt;已有条目会保留,重复 family 去重。C# 代码里直接写死的 Font = "..."u8 不会从源码字符串推断,仍需按上面的格式手工补行。 收集器接受 font/my_title、ui/font/Regular、带 res/ 前缀的写法,以及旧逻辑允许的具体字体文件(例如 font/my_title/my_title.ttf,按父目录取 family)。项目私有字体属于随包资源,只写入 fontref.txt,不会进入记录云资源依赖的 ref/dataref.txt。 共享字体别名会归一为对应 family:Regular、MiSans、Microsoft Yahei、Custom 指向 ui/font/Regular;SourceHanSerif、NoWarRounded、ALiMaMa、LXGWWenKai 指向各自的 ui/font family。表外裸名无法判断是 family 还是普通文本,收集器会跳过;这类字体请写完整 family 路径。

不要在 fontref.txt 里写注释行。当前加载逻辑会把每一行都当成字体 family 或字体路径。

运行期加载时机 ​

client+wasicore 会在游戏初始化时读取当前地图的 ref/fontref.txt 并预加载字体。预加载完成后,API>=7 的游戏运行中不允许动态加载新的字体 family。

因此:

  • 新增字体文件后,必须同时更新 ref/fontref.txt。
  • 修改 fontref.txt 后,需要重新进入地图或重启本局。
  • 运行中临时把 Label.Font 改成一个没预加载过的 family,不会生效。

数编 UI 的字体 family 已由生成流程自动收集;代码里的手写 family 仍需随代码一起维护 ref/fontref.txt,发布前按本文核对。

font_subset.txt 不是字体注册表 ​

ui/script/font_subset.txt 用来定义预裁剪字库的字符集合,目的是减小字体加载和内存成本。它不是字体 family 注册入口。

排查时先看 ref/fontref.txt 是否包含 family;只有当字体已经能切换,但某些字符首帧缺失或加载慢时,再考虑把常用字符加入 font_subset.txt。

支持的字体格式 ​

运行时支持常见 FreeType 字体和位图字体:

  • .ttf
  • .otf
  • .woff
  • .woff2
  • .ttc
  • .xml
  • .fnt
  • .sdf

一个 family 目录下可以放普通、粗体等多个字体文件。Label.Bold = true 时运行时会在同一 family 中匹配粗体风格;如果没有合适字重,会使用模拟粗体。

常见错误和修正 ​

错误问题修正
Font = "font/my_title/my_title.ttf"u8Label.Font 要 family,不要文件路径Font = "font/my_title"u8
Font = "res/font/my_title"u8项目 res/ 不写进资源路径Font = "font/my_title"u8
只复制字体文件,不改 fontref.txtfamily 没有预加载在 ref/fontref.txt 加 font/my_title
改完 fontref.txt 后不重进地图运行中不动态加载新 family重启本局或重新进入地图
字体缺失但文字仍显示默认 fallback 到 ui/font/regular查客户端日志里的 Invalid font、Can't find font family

Canvas 绘图字体 ​

Canvas 自定义字体走 NanoVG 接口,和 Label.Font 不是同一套注册机制:

csharp
#if CLIENT
var fontId = Canvas.CreateFont("MyCanvasFont", "font/my_title/my_title.ttf");

canvas.OnRender += (_, _) =>
{
    canvas.FontSize(24);
    canvas.FontFaceId(fontId);
    canvas.DrawText(20, 40, "Canvas text");
};
#endif

Canvas.CreateFont 接受具体字体文件路径,只影响 Canvas 绘图;它不会让 Label.Font = "font/my_title"u8 自动可用。Label/Input/TextButton 仍然需要 ref/fontref.txt。

排查清单 ​

  1. 字体文件是否在正确目录:项目私有字体放 <Project>/res/font/<family>/...。
  2. ref/fontref.txt 是否包含 family:例如 font/my_title。
  3. Label.Font 是否写同一个 family,而不是 .ttf / .otf 文件路径。
  4. 改动后是否重新进入地图。
  5. 客户端运行时日志是否有 Invalid font 或 Can't find font family。

相关路径规则见 项目资源路径指南。