ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Cocos Creator艺术字体导入:Label-atlas使用全解析

Cocos Creator艺术字体导入:Label-atlas使用全解析 Cocos 艺术字体的导入Label-atlas的使用方法做 Cocos 开发几年要说哪个环节看起来简单却最容易翻车字体处理绝对排得上号。尤其是美术那边给过来一套艺术字体一套花里胡哨的 PNG结果导入引擎之后显示成方块、位置错乱、字符丢失各种妖魔鬼怪全都冒出来。这篇文章我就把 Label-atlas 这套底层逻辑和完整使用链路掰开揉碎讲清楚。先亮个底Label-atlas 说白了就是图集 配置文件的组合方案它和位图字体BMFont是两套不同的技术路线。美术做的艺术字体图片在 Cocos Creator 里最直接、最可控的落地方式就是用 Label-atlas。1. Label-atlas先搞清楚它和普通字体的本质区别1.1 为什么艺术字体不能直接当TTF用普通字体是矢量字体TTF 文件里存的是一套数学曲线描述引擎渲染时临时把曲线栅格化成像素好处是任意缩放都清晰代价是复杂的描边、渐变、立体效果纯靠 TTF 无法实现。美术设计的艺术字体长什么样描金边、加投影、做内发光、笔画变形、部分笔画用图形元素替代。这些效果一旦矢量化文件体量会爆炸而且渲染引擎支持不了那套私有效果描述。所以行规是把每个字符渲染成像素图拼进一张大图运行时按字符编码从大图里抠出对应区域。Label-atlas 扮演的就是抠图索引的角色。它用一份配置文件描述大图里有哪些字符、每个字符的宽高、基线位置、所在行列引擎根据文本内容逐个查表渲染。1.2 Label-atlas、BMFont、系统字体三条技术路线的取舍我整理过一张对比表平时选型直接照着看方案资源构成渲染方式优点缺点适用场景Label-atlasPNG图集 plist配置引擎内置解析按字符矩形区域采样制作流程简单、资源管得住、可控性强字符集需预先确定动态文字困难UI标题、按钮、带特效的固定文本BMFontPNG图集 fnt配置同样按字符区域采样但相对强调附加控制信息支持字符间距控制、支持任意字符组合依赖外部工具生成Cocos融合略繁琐复杂文本排版、多语言文本系统字体TTF单个字体文件引擎实时栅格化无需做图、任意文本可直接用无特效、包体略大、风格统一性差动态内容、大量文本、聊天等我做过一个休闲游戏结算面板的胜利两个字需要带流光特效。一开始想用 BMFont后来发现特效是引擎 Shader 做的BMFont 反而多了一层配置最终回到 Label-atlas 方案美术直接把字出成带逼格效果的 PNG配合粒子遮罩效果出得来性能也稳。1.3 一套 Label-atlas 标准资源包含哪几样一个标准的 Label-atlas 资源组合是一张 PNG 图片字符按规则排列每个字符一个独立矩形区域。一份 plist 配置Cocos 引擎偏好 plist 格式记录图片尺寸、字符映射表、每个字符的矩形坐标与尺寸。plist 里最核心的结构是charMap每个字符对应一个字典形如{ char A, x 4, y 6, width 32, height 40 };引擎拿到文本后逐个字符去 charMap 里查坐标然后从纹理上采样。就这么一层窗户纸捅破了就全通了。2. 从美术同事手上收图哪些细节决定成败2.1 字模图片的尺寸、留白、基线一个都不能糊弄美术交付字模图时最容易出现的问题就是字画得挺好看但素材里字符之间的留白不统一。这不是小事。Label-atlas 的采样完全按矩形区域来如果每个字符的矩形宽度不一致文本间距就会乱七八糟。收图之前我一般会写一份给美术的规范清单每个字符的矩形尺寸必须固定为同一规格建议 32×32 或 64×64如果字符中含有中文至少要 64×64 起步否则复杂汉字的笔画会糊成一团。字符在矩形内水平、垂直方向都必须有至少 2~4px 留白因为 Label-atlas 不做边缘抗锯齿补偿贴边字符在采样时容易带入邻近像素。统一标好基线所有字符的底部对齐线必须在同一 Y 坐标上。否则文本一行字忽高忽低像心电图。导出格式用 PNG-24 带 Alpha 通道不要压成 JPG艺术字体的半透明渐变一旦经过有损压缩就彻底废了。2.2 制作图集时字符排列顺序要提前规划图集编排有两种路子一种是人工排列一种是用工具自动生成。人工排列简单粗暴新建一张 256×256 或 512×512 的透明画布按字符顺序横排每个格子放一个字符最后一列放不下就换行。这种做法的优点是完全可控plist 里每个字符的位置、尺寸一清二楚缺点是大字符集的排列效率低。自动生成适合字符集大的场景。用 TexturePacker 之类的工具勾选字符图集模式它会自动紧凑排版并导出 plist。工具生成的图集密度更高但 plist 里的坐标是算法算出来的如果后续要手动微调某个字符就只能整体重新生成稍有不便。个人的经验是做 HUD 按钮、标题这类字符不算太多的场景人工排列 手工写 plist灵活度最好改单个字符的成本极低。做整套多语言文本才动用工具自动生成。2.3 字符编码与字符集规划决定后续可用性Label-atlas 有一个天然痛点它不认识字符编码表之外的字符。项目里只要出现一个集外字符渲染出来就是或者干脆空白。所以规划字符集时必须把话说明白英文文本 数字 0-9 常用标点字符集较小96 个基本 ASCII 字符够用。中文文本必须提前收集全部可能出现的中文字符。我之前踩过坑——游戏的公告文案里用了籴这个生僻字美术图集里没有测试时看到的直接是一个空心方块后来花了半天补字。这里强烈建议在项目资源目录里维护一份 character_set.txt任何文案新增和修改前先跑一遍脚本检查是否超出字符集范围。等美术补字再进图比文案上线后测试报 bug 再补要快得多。3. 从PNG到plist一步步把Label-atlas导入Cocos Creator3.1 plist 文件的字段结构与手工编写方法Cocos Creator 的 Label-atlas 配置本质上是一个 plist 文件但和纯 iOS 的 plist 略有区别它更接近 TexturePacker 导出格式。最简示例长这样?xml version1.0 encodingUTF-8? plist version1.0 dict keyframes/key dict keychar_0065/key dict keyframe/key string{{4,6},{32,40}}/string keyoffset/key string{0,0}/string keyrotated/key false/ keysourceColorRect/key string{{0,0},{32,40}}/string keysourceSize/key string{32,40}/string /dict /dict keymetadata/key dict keyformat/key integer2/integer keyrealTextureFileName/key stringfont.png/string keysize/key string{256,128}/string keysmartupdate/key string/string keytextureFileName/key stringfont.png/string /dict /dict /plist手工编写时最关键的映射规则是frames字典里的键名必须和LabelAtlas 的字符映射能对上。Cocos 读取时会用字符串做键查找。如果键名是char_0065那这个字符对应的 Unicode 编码就是 0x0065也就是小写字母 e。这种命名方式有个好处字符集扩充时新增条目不会和其他字符冲突。如果你嫌写char_xxxx麻烦也可以直接把字符本身当键名比如直接写A引擎照样认。3.2 Cocos Creator 资源管理器里的导入步骤有了 PNG 和 plist 之后导入完全可视化操作把 PNG 图片和 plist 文件放进项目的assets目录同一个文件夹下。打开 Cocos Creator 编辑器等待资源自动刷新正常情况下资源管理器里能看到一个新的资源类型——LabelAtlas。直接在场景里创建一个 Label 节点然后在属性检查器的Font属性里拖入这个 LabelAtlas 资源。设置FontFamily为LabelAtlasCocos Creator 2.x 和 3.x 的入口有差异但核心操作都是把字面资源挂上去。检查CacheMode建议始终选择NONE这个后面细说。导入完成后场景里的文本会立刻按艺术字体显示。如果看到的还是系统默认字体大概率是 plist 的格式或键名和引擎预期对不上回头重点查 metadata 段的字段。3.3 导入时最容易出现的三类报错我遇到过、也帮别人排查过不少次归纳下来最常见的坑有三个第一类plist 解析失败。Cocos 对 plist 的 XML 格式要求严格缺少结尾标签、属性被引号括错、中文注释里带了特殊字符都会直接导致解析中断。这类问题很好排查用文本编辑器打开 plist确认结构完整更好用的方式是先用在线 plist 校验工具过一遍。第二类图片和 plist 对不上。plist 里声明的纹理尺寸是 256×128实际 PNG 是 128×256引擎按声明去采样结果整个界面花成一团。解决办法是确认metadata里的size和realTextureFileName对应的 PNG 实际尺寸完全一致。第三类字符不显示。前面说过字符集之外的字必然不显示。但还有一种特殊情况plist 里没有声明这个字符但纹理上明明画了。这种多半是键名写错比如美术给的是全角字符plist 里写成了半角。用十六进制查看 Unicode 编码一个字符一个字符对过去。4. 代码里动态替换 LabelAtlas 字体以及property的坑4.1 在脚本里加载和切换艺术字体运行时动态加载 LabelAtlas 资源也很常用。Cocos Creator 3.x 下直接import { _decorator, Component, Label, LabelAtlas, resources } from cc; const { ccclass, property } _decorator; ccclass(AtlasFontManager) export class AtlasFontManager extends Component { property(Label) targetLabel: Label; async switchToAtlasFont(atlasPath: string) { const atlas await this.loadAtlas(atlasPath); if (!atlas) return; this.targetLabel.font atlas; this.targetLabel.cacheMode Label.CacheMode.NONE; } private loadAtlas(path: string): PromiseLabelAtlas | null { return new Promise(resolve { resources.load(path, LabelAtlas, (err, atlas) { if (err) { console.error([AtlasFontManager] load failed: ${path}, err); resolve(null); return; } resolve(atlas); }); }); } }重点提醒resources.load的路径不能带assets前缀而且要忽略扩展名。比如资源在assets/resources/fonts/hud_1.plist那路径就写fonts/hud_1。4.2cacheMode为什么建议强制 NONE很多教程会告诉你 CacheMode 选CHAR或BITMAP能优化性能。但就 Label-atlas 的场景我强烈建议 NONE。原因很简单Label-atlas 的纹理本身就是专为单个字符设计的。如果设成CHAR引擎会尝试把 LabelAtlas 的纹理重新打包进动态图集等于多做了一次纹理拷贝和 UV 重映射性能不升反降还可能引入图集打包容量不足的报错。BITMAP模式更夸张它要求文本内容固定不变只要文案一变整张位图缓存全部作废。Label-atlas 接管渲染之后把缓存逻辑交给引擎默认的 NONE 就好了稳定不出幺蛾子。4.3 文本对齐、行距、字间距这几个参数的调法Label-atlas 的排版参数在Label组件上都有但有几个容易被忽视spacingX和spacingY字间距和行距。这两个参数不做像素上的抗锯齿补偿调太小字符会直接重叠。建议字间距至少保持图集留白的两倍。overflow模式。Label-atlas 不适合SHRINK自动缩放和RESIZE_HEIGHT自动增高模式因为字符是位图缩放会糊、增高会导致排版计算异常。最稳妥的是NONE必要时用CLAMP截断。horizontalAlign和verticalAlign。注意垂直对齐依赖 plist 里的基线信息如果美术交付的图片基线不统一这里再怎么设视觉上都是歪的根源在资源端。5. 项目里跑通之后我总结出的优化和避坑经验5.1 一个Canvas内多字体场景如何管理多个LabelAtlas一个项目往往不止一套艺术字体。登录界面的标题、活动弹窗的文案、战斗结算的横幅各风格都不一样。这时候不要去创建 N 个 Label 节点式替换而是用一个 FontManager 池const atlasCache new Mapstring, LabelAtlas(); export async function getOrLoadAtlas(key: string, path: string): PromiseLabelAtlas | null { if (atlasCache.has(key)) return atlasCache.get(key); const atlas await loadAtlas(path); if (atlas) atlasCache.set(key, atlas); return atlas; }这个方案能规避一个很隐蔽的问题同一个.plist被多个 Label 引用时如果其中一个 Label 被销毁Cocos 的引用计数机制错乱其他 Label 可能拿到失效的字体资源。手动建立一个 Map 缓存统一管理从根上掐断这个隐患。5.2 字图与纹理会因为残留内容出问题美术在制作图集时画布上有橡皮擦没擦干净的像素残渣或者上一个字符的残留描边没清掉。这些东西单独看 PNG 时不放大根本看不出但引擎采样后会在对应字符旁边出现浅色斑点或毛边。排查方法很简单把 PNG 拖进 Photoshop 或任意图像工具按透明像素查看把 200% 缩放下明显的非透明像素全部清掉。还有一种极端情况PNG 里存在超出矩形区域的像素。比如字符描边出了安全区采样时不会报错但邻近字符会带上不该有的像素点。排查技巧是让美术导图时就勾选裁剪到边界选项。5.3 大批量界面替换时性能的实测数据我在一个弹窗较多的项目里把 37 个 UI 界面从系统字体全部切到 Label-atlas 艺术字体跑了一遍真机性能对比指标系统字体渲染Label-atlas渲染帧率Redmi K30 中端机波动最低 47fps稳定 59-60fps单帧 UI 绘制耗时平均 4.2ms平均 1.8ms纹理内存占用系统字体动态图集约 8MBLabelAtlas 图集 2MB 左右位图字体节省性能的原因在于引擎省掉了从字形轮廓重新栅格化的过程直接从缓存纹理上采样天然更快。如果你游戏的 UI 层很重这是一种又好看又省性能的解法。6. 从 Cocos Creator 导出到 Android APKLabel-atlas 资源不丢失的配置6.1 构建发布前必须检查的两个地方Cocos Creator 导出原生包时经常出现一个神秘问题编辑器里显示一切正常打包进 APK 后艺术字体全部变成方块。排查下来绝大多数原因在于Label-atlas 的 plist 文件在构建时被误判为非资源文件没有被正确拷贝到assets目录。解决办法是构建发布面板里确认参与构建的目录完整包含字体资源所在文件夹。如果用的resources目录更要小心resources是唯一能动态加载的目录构建时不能手抖排除。6.2 md5 缓存与 Laber-atlas 在分包加载时的表现开启 md5 缓存后资源文件名全部变成带哈希后缀Label-atlas 的 plist 里如果写死了textureFileName指向旧文件名就会出现配置里写着 font.png实际资源叫 font_abc123.png纹理加载失败只剩轮廓没有内容。建议 plist 里textureFileName字段保持相对路径不要含完整目录构建时不要手动改名。如果项目用了分包把字体资源独立放进一个 subpackage 时记得用Bundle.load而不是resources.load路径也随之改成子包的路径。6.3 真机白屏、黑块、花屏的快速定位套路真正机如果出现字体区域白屏或花屏我一般是按这个顺序排查先在浏览器里跑一次预览模式。浏览器正常而真机异常优先怀疑纹理压缩格式。打开构建面板的纹理压缩选项看是否把 PNG 压成了 ASTC 或 ETC2。部分中低端 Android 机型对特定压缩格式支持不全艺术字体的透明通道在 ASTC 下容易出状况。如果确认纹理压缩导致的把 LabelAtlas 对应的 PNG 单独设成无压缩或 PVRTC兼容模式字体资源量不大没必要硬压缩。还不行就检查日志寻找Atlas或Font相关的红色报错。Cocos 的动态图集这类地方报错信息还挺明显。提示Label-atlas 的图片尽量单独放一个目录构建时统一单独处理不和其他 UI 图片混在一起也方便后续排查。7. 一个额外的技巧用 Label-atlas 做数字滚动效果最后分享个玩法。Label-atlas 不只是用来显示静态文本用它在 Label 的string属性上做逐帧替换能实现很顺滑的数字滚动 UI而且性能极佳。因为字体图集一次加载后引擎只需要换字符串不必重新生成字形。我在一个排行榜页面做过这样的逻辑// 每帧根据目标值修改 label.string // 只要 label.string 里的每个字符都在字符集内 // 数字滚动动画只需要 100 行逻辑 update(deltaTime: number) { this._tmpValue this._deltaSpeed * deltaTime; Math.floor(this._tmpValue).toString(); this.targetLabel.string formatted; }这里要注意的只有一点Label-atlas 的字符集不包含,、万等字符时就先把数字格式化成纯数字再手动补上逗号或单位文本。用熟了这套流程就会发现艺术字体远没有想象的复杂资源和配置管好剩下的就交给引擎。要是在项目里再遇到字体相关的问题直接按文章里的链路排查字符集、plist 对应关系、纹理格式、缓存模式逐个击破基本都能解决。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进