主题
自定义字体指南
本文说明如何在 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.ttfref/fontref.txt 写:
text
font/my_title字体文件放在哪里
| 用途 | 文件位置 | family / 代码路径 |
|---|---|---|
| 项目私有随包字体 | <Project>/res/font/my_title/my_title.ttf | font/my_title |
| 公共 UI 字体或官方字体 | ui/font/Regular/Regular.otf | ui/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"u8 | Label.Font 要 family,不要文件路径 | Font = "font/my_title"u8 |
Font = "res/font/my_title"u8 | 项目 res/ 不写进资源路径 | Font = "font/my_title"u8 |
只复制字体文件,不改 fontref.txt | family 没有预加载 | 在 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");
};
#endifCanvas.CreateFont 接受具体字体文件路径,只影响 Canvas 绘图;它不会让 Label.Font = "font/my_title"u8 自动可用。Label/Input/TextButton 仍然需要 ref/fontref.txt。
排查清单
- 字体文件是否在正确目录:项目私有字体放
<Project>/res/font/<family>/...。 ref/fontref.txt是否包含 family:例如font/my_title。Label.Font是否写同一个 family,而不是.ttf/.otf文件路径。- 改动后是否重新进入地图。
- 客户端运行时日志是否有
Invalid font或Can't find font family。
相关路径规则见 项目资源路径指南。