字体与打包
让 DreamGUI 的文本在打包后的游戏里和在编辑器里看起来一样:哪些字体自己会跟着走、怎么加彩色 emoji、游戏要带哪套 ICU 数据,以及怎么检查一个包。
这一页讲的是一段文本在打包后的游戏里要和编辑器里一样,需要什么:哪些字体自己会跟着走、哪些要你加, 怎么加一个彩色 emoji 字体,游戏要和哪套 ICU 数据一起打包,以及怎么检查一个包。这里说的都是插件在 Win64 上的行为; 没有别的平台构建过(见平台)。
自己会跟着走的
默认字体
/DreamGUI/DefaultFont_DistanceField 是 Roboto 的四个真实字面(常规、粗体、斜体、粗斜体),后面垫着 DroidSansFallback
负责中文、日文和韩文,每个字面都在轮廓多通道距离场上(怎么构建的见插件的 Tools/Fonts/README.md)。这五个资产都指向一个
引擎字体字面(FontType 为 Engine Font):
- 烹饪时,
BeginCacheForCookedPlatformData把每个字面的字节拷进它的资产。编辑器之外,字体只读这些字节,所以什么都不用额外 stage。 /DreamGUI总会被烹饪:插件的Config/Game.ini把它放进了DirectoriesToAlwaysCook,所以即使按显式包列表烹饪(-map=、chunk)也有它。- DroidSansFallback 给包增加大约 3.9 MB。
按语言选字面
字体的 Fallbacks 条目可以写明自己是给哪些文化用的(Cultures,如 "zh-Hans"、"ja"):语言是这个文化的文本
(它的 Language,或富文本里的 <lang=xx>)会在字体的其它后备之前先试这个字面;条目开了 bPreferOverPrimary 时,
甚至排在字体自己的字面之前。
优先用这种条目,而不是 bCultureFont 和它的 CultureFontMap。那个开关在游戏文化改变时替换字体自己的字面,而它只对
加载策略为 Inline 的引擎字体字面有效:烹饪后 Lazy Load 或 Stream 的字面没有可供替换的字面数据,字体会打一条警告,
继续用烹饪时的那个字面。
彩色 emoji
默认字体有意不带彩色 emoji 字面。引擎的 NotoColorEmoji.ttf 在 Engine/Content/Editor/Slate/Fonts 下,那是打包后的游戏
拿不到的编辑器内容(Slate 只在编辑器里加载它);而把它嵌进插件的字体,会给每个用 DreamGUI 的项目增加 7.8 MB。
没有彩色字面时,一个 emoji 有 EmojiData 条目(每个 emoji 一张图)就从那里画,否则从任何含有这个码位的单色字面画,
或者画成缺字方框。
选字体,并留意许可证
DreamGUI 能画 CBDT/CBLC 和 sbix 位图(Noto Color Emoji 属于前者)以及 COLRv0 图层的彩色字形。只有 COLRv1 或 SVG 数据的 字形算作缺字,WOFF2 字体则根本加载不了(引擎的 FreeType 没有 Brotli)。许可证各不相同:Noto Color Emoji 是 OFL —— 要随游戏附上它的许可证;Twemoji 的图是 CC-BY;Segoe UI Emoji 不允许再分发。
给它建一个字体资产,三选一
先把字体文件拷进项目,然后为它建一个距离场字体资产(内容浏览器里的 DreamUI FontData DistanceField)。 它自己的设置几乎无关紧要:后备字形是画进文本所用那个字体的图集的,彩色字形按它自己的像素尺寸画,而不是画成距离场。
自定义字体文件,嵌入资产
FontType 为 Custom Font File,bUseExternalFileOrEmbedInToUAsset 关(默认)。编辑器读文件,把字节存进资产。
烹饪时如果文件还在,会再读一遍,所以文件改过之后没重存的资产不会发出过期的字节;烹饪时找不到可嵌入的东西是一个错误,
而不是悄悄发出一个没有字面的字体。
引擎字体字面
FontType 为 Engine Font。把文件导入成一个 Font Face,填进 EngineFont;烹饪时拷贝它的字节,和默认字体一样。三种里最稳的一种。
外部文件
bUseExternalFileOrEmbedInToUAsset 开。游戏在运行时读这个文件,所以它必须被 stage:对 Content/Fonts 下的文件,在
Config/DefaultGame.ini 的 [/Script/UnrealEd.ProjectPackagingSettings] 下加
+DirectoriesToAlwaysStageAsUFS=(Path="Fonts");FontFilePath 必须相对于项目目录(bUseRelativeFilePath)。
绝对路径在打包后的游戏里永远不起作用。
把它加成文本字体的后备,只管 emoji
在文本字体的 Fallbacks 里加一个条目,让它的 Ranges 只覆盖 emoji、别的都不管,这样它的数字、空格和符号永远不会顶替
文本字体自己的:
| 范围 | 是什么 |
|---|---|
0x23、0x2A、0x30-0x39 | 键帽的底字(#、*、数字),用于 1 U+FE0F U+20E3 这样的序列 |
0xA9、0xAE | (c) 和 (r) |
0x203C-0x3299 | 符号、箭头、装饰符号和带圈表意字中的 emoji |
0x1F000-0x1FAFF | emoji 区块,含旗帜 |
范围匹配的是一个字簇的第一个码位;后面跟着的连接符、变体选择符、肤色和标签不需要自己的范围。Cultures 留空,Scale 保持 1。
文本字体必须能装颜色。 它要么在轮廓多通道距离场上(SdfSource 为 Outline Multi Channel,默认),要么是位图字体。
单通道距离场上的字体图集是 R8 —— 一个通道、没有颜色 —— 一个彩色字形也画不了:它的 emoji 会退回 EmojiData 或单色字面。
要求 emoji 呈现的字簇 —— 默认以 emoji 呈现的象形符号、后面跟 U+FE0F 的任何字符、旗帜、键帽、肤色、ZWJ 序列 —— 先试彩色字面
(字体的 bPreferColorEmoji,默认开)。其余都是文本呈现,先试单色字面,所以这些范围里的数字和符号只要文本字体有,仍然来自文本字体。
针对完整序列的 EmojiData 条目仍然优先于彩色字面。
自己的材质。 彩色字形、小字号的覆盖率字形和渐变绘制,都由 DreamGUI 自己的着色(MF_DreamUI_Shade)解码;
一个材质带着这个函数的 DreamUI_ShadeMarker 参数时,才算经过它着色,DreamGUI 自己的材质都带着。用的材质不经过它时,
文本会按"字体没有彩色字面"来排 emoji,而不是把一张彩色位图当距离场去画;它也拿不到覆盖率字形,渐变改用顶点色绘制。
代价
Noto Color Emoji 的字节大约占 7.8 MB 内存,由它的字体资产持有;彩色字形本身在文本字体的图集里,每个 emoji 每个尺寸档一个。
一个图集切片默认是 2048 × 2048 BGRA:GPU 上 16 MiB,字体在 CPU 上保留的副本再占同样多,每个字体最多 MaxFontAtlasSlices(8)片。
控制台命令 DreamGUI.Memory 打印每个字体的图集装了什么 —— 切片、GPU 和 CPU 字节、格子、距离场字形、彩色字形和覆盖率字形 ——
DreamGUI.Memory Json 以 JSON 输出同样的内容。
库在打包后的游戏里做什么
打包后的游戏链接的库和编辑器相同:
- FreeType 2.14.1(引擎的 Win64 Release 库):PNG 位图(CBDT、sbix)和 COLRv0 图层可用;没有 Brotli,所以 WOFF2 字体加载不了; SVG 字形被跳过,只有 COLRv1 的字形算作缺字。
- HarfBuzz 2.4.0 在除专用服务器之外的所有目标上做字形整形;专用服务器不画文本。
- ICU 64 负责断行、断词,并回答一个文化会退回到哪些名字。
Emoji_Presentation和Extended_Pictographic编译在库里, 所以 emoji 检测在任何打包预设下都可用;更新的 emoji 来自插件自己的表。
只用过 Win64 的库,这些库在其它平台的构建从没被链接进 DreamGUI。打包后的游戏实际怎样,由打包文本冒烟测试(见下文)检查; 在发布闸门为一个版本跑过它之前(见平台),这一节说的"打包后的游戏",其实是编辑器用同样的库所做的事。
ICU 数据:打包 EFIGSCJK,或者 All
游戏带多少 ICU 数据由打包预设决定:Project Settings ▸ Packaging ▸ Internationalization Support(InternationalizationPreset),
默认是 English;BuildCookRun 的 -I18NPreset= 可以覆盖它。编辑器总是有全部数据,所以文本可能在编辑器里一种排法、在包里另一种。
任何有中文、日文或韩文文本的游戏都用 EFIGSCJK 打包;有泰文、老挝文、高棉文或缅甸文的用 All。 在 English 预设下:
- 游戏根本无法把文化切换到中文或日文。
- 断行失去词典。 English 只带最基本的字符、单词和行规则。CJK 文本于是在任意两个字符之间断开,没有短语词典
(
PhraseWrap,即 CSS 的word-break: auto-phrase,退回逐字断行),也没有日文和中文的断行裁剪规则; 泰文、老挝文、高棉文和缅甸文需要只有All才带的词典,只能在空格处断行。
中文按书写系统退回
引擎通过 ICU 的 likely subtags 找出一个文化用哪种书写系统,而 English 和 EFIGSCJK 的数据都不带它:在打包后的游戏里,
"zh-CN" 只退回到 zh-CN, zh,而编辑器里是 zh-Hans-CN, zh-CN, zh-Hans, zh。DreamGUI 给中文名补回了书写系统 ——
zh-CN、zh-SG 和 zh 补 Hans,zh-TW、zh-HK 和 zh-MO 补 Hant —— 所以给 "zh-Hans" 和 "zh-Hant" 的后备条目在包里
和在编辑器里一样能匹配上。没有这一步,这类条目在包里永远匹配不到一段 zh-CN 文本,一个同时有日文和简体中文后备的字体
就可能把汉字画成日文字形。其它语言的条目按语言和地区匹配,那是每个预设都有的。
断行跟随游戏的文化
行与词的迭代器按当前文化的 locale(FInternationalization 的当前文化)创建,文化改变时重新创建。早先它们跟的是操作系统的语言,
同一个游戏在日文 Windows 和英文 Windows 上断日文行的方式不同。
检查一个包
- 在打包游戏(Development)的控制台里,
DreamGUI.Memory打印字体、精灵图集和画布占了什么;DreamGUI.Memory File=<path>把它写成 JSON,相对路径写在Saved/下。 - 插件测试宿主里的打包文本冒烟测试,把一屏这些情况 —— 小字号文本、混排的一行、按文化后备的中文和日文、彩色 emoji、两端对齐的段落、
日文段落、安全区 —— 分别放进一个打包游戏和同一构建在未烹饪内容上的运行里,逐字段比对两边(安全区各自对照自己的视口)。
发布闸门在打标签之前跑它。怎么跑见
Tools/TestHost/README.md的 "The packaged text smoke test" 一节和测试。