1. 项目缘起与核心需求拆解
做Unity开发的朋友大概率都踩过同一个坑:UI界面里用默认的Text组件显示中文,字体糊得像隔了一层毛玻璃,放大之后边缘全是锯齿,笔画稍微复杂一点的字直接糊成一团。后来换了TextMeshPro,英文数字确实清晰了,结果一输入中文——要么显示成方块,要么干脆什么都不显示,控制台也不报错,让人一头雾水。
这个项目的核心就是解决TextMeshPro显示中文的问题。TextMeshPro(后面统一简称TMP)是Unity官方推荐的高性能文本渲染方案,基于Signed Distance Field技术,理论上可以做到任意缩放都不糊。但它默认只带了一套拉丁字符集,中文这种动辄几千上万字形的语言,需要我们自己生成字体图集才能正常使用。
说白了,这篇内容要解决的就是三件事:第一,让TMP能正确显示中文;第二,让显示出来的中文清晰、不糊、不丢字;第三,把整个流程的成本控制住,别为了几个界面文字把包体撑爆。适合所有正在用Unity做中文项目、被TMP中文问题卡住的开发者,不管你是刚接触TMP的新手,还是已经用过一段时间但没系统整理过的老手,都能从这里找到可以直接复现的方案。
我自己的项目从最初用默认Text到全面切换TMP,中间踩的坑少说也有七八个,今天就把这套流程完整梳理一遍,把每个环节的“为什么”讲清楚,让你不光会操作,还知道背后的逻辑。
2. 核心原理与方案选型解析
2.1 为什么TMP默认显示不了中文
要理解这个问题,得先搞清楚TMP的工作机制。TMP不像传统的Text组件那样直接调用系统字体来渲染,它用的是预烘焙的字体图集。简单说,就是把需要的字符提前“画”到一张纹理图上,运行时通过UV坐标去采样。这样做的好处是渲染效率极高,每个字符只需要两个三角形,而且缩放不会失真。
但问题也出在这里:默认的TMP字体资源(比如LiberationSans SDF)只包含了ASCII字符集,也就是英文、数字和常用符号。中文字符根本不在图集里,TMP找不到对应的字形数据,自然就显示不出来。这就像你拿了一本只有26个字母的字典去查汉字,查不到是正常的。
所以解决方案的核心思路就是:制作一个包含中文字形的TMP字体资源。这个过程叫“烘焙字体图集”,本质上是把中文字符渲染到一张或多张纹理上,同时生成对应的字形度量信息。
2.2 字体图集的两种生成模式怎么选
TMP提供了两种字体图集的生成模式,这个选择直接影响到包体大小和显示效果,必须搞清楚。
静态模式(Static):你手动指定需要包含哪些字符,TMP把这些字符烘焙到图集里。优点是运行时不需要额外计算,性能最好,图集大小可控。缺点是如果遇到图集里没有的字,照样显示不出来。适合字符集固定的场景,比如界面按钮文字、固定标题这类。
动态模式(Dynamic):图集在运行时按需生成,遇到没有的字符就实时添加进去。优点是灵活,什么字都能显示。缺点是首次显示某个字时会有轻微的性能开销,而且图集可能随着使用不断增大,需要设置合理的上限。适合聊天、玩家昵称、剧情文本这类不可预知的场景。
实际项目中,我通常采用混合策略:常用字(比如GB2312一级字库的3755个字)用静态图集预烘焙,保证界面文字零延迟;动态图集作为补充,处理那些生僻字和玩家输入。这样既保证了性能,又兼顾了灵活性。
2.3 字符集范围怎么确定
中文到底有多少字?这个问题的答案直接决定了你的图集有多大。我整理了一个对照表,方便你根据项目需求做选择:
| 字符集范围 | 字符数量 | 图集大小(1024x1024,SDF 16x) | 适用场景 |
|---|---|---|---|
| ASCII | 128 | 极小 | 纯英文项目 |
| GB2312一级 | 3755 | 约2-3张 | 常规中文界面 |
| GB2312全量 | 6763 | 约4-6张 | 完整中文支持 |
| GBK | 21886 | 约15-20张 | 生僻字较多的项目 |
| Unicode常用 | 约20000+ | 20张以上 | 多语言混合项目 |
这里有个经验值:一张1024x1024的SDF图集,在16x16的采样精度下,大约能容纳2000-2500个中文字符。如果你选了3755个一级字,大概需要2张图集。这个数字不是绝对的,跟字体本身的复杂度有关,笔画多的字占用的空间会大一些。
注意:不要一上来就选GBK全量。我见过有项目直接烘焙了2万多个字,结果图集占了快30MB,包体直接超标。先想清楚你的项目真的需要那么多字吗?
3. 实操流程与关键环节实现
3.1 准备工作:字体文件的选择与处理
第一步是选字体。TMP支持TTF和OTF格式的字体文件,但不是什么字体都能直接用。这里有几个硬性要求:
- 字体必须包含你需要的中文字形。有些英文字体虽然好看,但压根没有中文字形,这种直接排除。
- 注意字体的授权协议。商业项目要确保字体可以商用,开源字体比如思源黑体、文泉驿系列都是不错的选择。
- 字体文件不宜过大。有些字体文件动辄几十MB,烘焙出来的图集也会相应变大。
我个人的习惯是,先把字体文件拖进Unity的Assets目录,然后在Project窗口里选中它,在Inspector面板确认一下Font Size和Rendering Mode的设置。对于中文SDF烘焙,建议把Rendering Mode设为Smooth,这样生成的SDF边缘过渡更自然。
接下来创建一个TMP的Font Asset。右键菜单选择Create > TextMeshPro > Font Asset,然后把你的TTF/OTF字体文件拖到弹出的窗口里。这时候TMP会自动打开Font Asset Creator面板,这就是我们接下来要操作的核心界面。
3.2 Font Asset Creator参数详解
这个面板里的参数比较多,我逐个说明,把每个参数背后的逻辑讲清楚。
Source Font File:这里显示你选择的字体文件。确认一下是不是你想要的那个。
Sampling Point Size:采样点大小,决定了每个字符渲染时的精细程度。这个值越大,字符的细节保留越多,但图集占用也越大。对于中文,我建议设置在32到48之间。设太小(比如16),笔画密集的字会糊;设太大(比如72),图集数量会暴涨。我实测下来,36是一个比较均衡的值,既能保证清晰度,图集大小也可接受。
Padding:字符之间的间距,单位是像素。这个值影响的是SDF的过渡区域。设太小,字符边缘会有裁切感;设太大,浪费图集空间。一般设置5到9就够了,我通常用7。
Packing Method:打包方式,有Fast和Optimum两种。Fast速度快但排列不够紧凑,Optimum速度慢但空间利用率高。对于中文字体这种大批量烘焙,建议选Optimum,虽然多等几分钟,但能省下不少图集空间。
Atlas Resolution:图集分辨率,可选512到8192。这个值决定了单张图集能容纳多少字符。对于中文,1024x1024或2048x2048是常见选择。如果你的字符集比较大,TMP会自动生成多张图集,所以不用一次性设得太大。
Character Set:字符集选择,这是最关键的一步。下拉菜单里有几个选项:
- ASCII:只包含英文数字符号
- Extended ASCII:扩展ASCII
- Unicode Range:手动指定Unicode范围
- Characters from File:从文本文件读取字符
- Custom Range:自定义范围
对于中文,最常用的方式是Characters from File。你可以准备一个包含所有需要字符的文本文件,然后让TMP从这个文件里读取。这样做的好处是精确控制,不会浪费图集空间在不需要的字符上。
如果你不想手动整理字符文件,也可以用Unicode Range,输入4E00-9FFF覆盖CJK统一汉字的基本区。但这个范围包含了2万多个字,烘焙出来的图集会非常大。所以我还是推荐用文本文件的方式,把常用字整理出来。
Render Mode:渲染模式,有SDFAA、SDFAA_HINTED、SMOOTH等选项。对于中文,SDFAA是默认推荐,兼顾质量和性能。如果发现小字号下笔画粘连,可以试试SDFAA_HINTED,它会对齐像素网格,改善小字号的清晰度。
3.3 生成中文字符集的实操方法
前面提到用文本文件来指定字符集,这里详细说一下怎么准备这个文件。
最省事的办法是从现有的字库标准里提取。比如GB2312一级字库的3755个汉字,网上有现成的字符表,直接复制到一个txt文件里就行。注意保存的时候要选UTF-8编码,否则TMP读取的时候会乱码。
如果你想要更精细的控制,可以按项目实际用到的文字来整理。比如你的游戏界面文字是固定的,那就把所有界面文案里的汉字提取出来,去重之后保存成文件。这样图集可以做到最小。
我自己的做法是分两步走:先准备一个包含GB2312一级字库的完整文件,作为基础字符集;然后针对具体项目,再额外补充一些项目特有的字。这样既保证了通用性,又不会遗漏。
实操心得:整理字符文件的时候,记得把标点符号也加进去。中文标点比如逗号、句号、引号、顿号这些,如果不包含在图集里,显示的时候也会变成方块。我见过有人只加了汉字忘了标点,结果界面上全是方块,排查了半天。
3.4 烘焙过程与参数验证
参数设置好之后,点击Generate Font Atlas按钮,TMP就开始烘焙了。这个过程的时间取决于字符数量和图集分辨率,几千个字的话大概需要几十秒到几分钟。
烘焙完成后,面板下方会显示图集的预览图。你需要重点检查几个地方:
- 字符是否完整:预览图里应该能看到你指定的所有字符。如果有缺失,检查字符文件是否正确。
- 图集数量:面板会显示生成了几张图集。如果数量太多(比如超过10张),说明你的字符集太大或者图集分辨率设小了。
- 字符清晰度:放大预览图,看看笔画复杂的字(比如“囊”、“懿”这种)是否清晰可辨。
确认无误后,点击Save按钮,把生成的Font Asset保存到项目里。TMP会生成一个.asset文件,这就是我们后面要用的字体资源。
3.5 在场景中应用与验证
保存好Font Asset之后,使用方法很简单:选中场景里的TextMeshPro组件,在Inspector面板的Font Asset字段里,把刚才生成的字体资源拖进去。如果一切正常,中文应该就能正确显示了。
但这里有几个容易忽略的细节:
材质球的问题:TMP的字体资源会附带一个材质球(Material)。如果你在Font Asset的Inspector里修改了材质参数,记得点击Material Preset来保存,否则场景里的文字可能不会更新。
Fallback字体:如果你只烘焙了常用字,遇到生僻字还是会显示方块。这时候可以设置Fallback字体列表,让TMP在主字体找不到字形时,自动去备用字体里查找。设置方法是选中Font Asset,在Inspector的Fallback Font Assets列表里添加备用字体。
动态字体的补充:如果你的项目需要显示玩家输入的任意文字,建议再创建一个动态模式的Font Asset作为Fallback。这样遇到图集里没有的字,TMP会实时生成,虽然有一点点性能开销,但保证了显示完整性。
4. 常见问题与排查技巧实录
4.1 中文显示为方块或空白
这是最常见的问题,原因通常有三个:
原因一:字体资源没有正确赋值。检查TextMeshPro组件的Font Asset字段,确认已经指向了包含中文的字体资源。有时候场景里有多个TMP组件,可能只改了一个,其他的忘了改。
原因二:字符不在图集里。如果你用的是静态图集,而显示的字符不在烘焙范围内,就会显示方块。解决办法是把缺失的字符补充到字符文件里,重新烘焙;或者设置动态Fallback字体。
原因三:材质球丢失或损坏。TMP的字体资源依赖材质球来渲染,如果材质球丢了,文字会显示异常。检查Font Asset的Material字段是否为空,如果是,重新指定一下。
4.2 中文显示模糊或有锯齿
这个问题通常跟SDF的采样参数有关。排查思路如下:
- 检查Sampling Point Size:如果设得太小(比如16),笔画密集的字会糊。建议至少32以上。
- 检查Padding:Padding太小会导致SDF过渡区域不足,边缘出现硬切。建议5-9之间。
- 检查图集分辨率:如果图集分辨率太低,字符占用的像素太少,也会糊。可以适当提高图集分辨率,或者减少单张图集的字符数量。
- 检查材质球的Sharpness参数:TMP的材质球里有一个Sharpness参数,调整它可以改变边缘的锐利程度。默认值0.5,可以试着调到0.6-0.7看看效果。
4.3 图集太大导致包体超标
这是很多项目上线前才会发现的问题。排查和优化思路:
| 问题表现 | 可能原因 | 优化方案 |
|---|---|---|
| 图集数量超过10张 | 字符集太大 | 精简字符集,只保留项目实际用到的字 |
| 单张图集分辨率过高 | Atlas Resolution设太大 | 降到1024或2048 |
| 图集文件体积大 | Sampling Point Size过高 | 降到32-36 |
| 多套字体资源冗余 | 每个界面用了不同的字体资源 | 统一使用一套字体资源 |
我自己的经验是,一个常规的中文手游项目,字体图集控制在3-5MB是比较合理的。如果超过10MB,就要认真检查是不是有优化空间。
4.4 动态字体图集不断增大
使用动态模式时,图集会随着新字符的加入不断增大。如果不加控制,可能跑着跑着内存就爆了。解决办法:
- 设置Atlas Population Mode为Dynamic,然后在Font Asset的Inspector里设置Atlas Width和Height的上限。
- 启用Multi Atlas Textures,让TMP在单张图集满了之后自动创建新图集,而不是无限扩大单张图集。
- 定期清理不用的字符。TMP提供了
TMP_FontAsset.ClearFontAssetData()接口,可以在场景切换时清理动态图集,释放内存。
4.5 常见问题速查表
| 问题现象 | 排查方向 | 快速解决 |
|---|---|---|
| 中文显示方块 | 字体资源、字符集、材质球 | 检查Font Asset赋值,确认字符在烘焙范围内 |
| 中文模糊 | 采样参数、图集分辨率 | 提高Sampling Point Size到32+,Padding到7 |
| 部分字缺失 | 字符集不完整 | 补充字符文件重新烘焙,或设置Fallback |
| 包体过大 | 图集数量、分辨率 | 精简字符集,降低图集分辨率 |
| 动态图集内存增长 | 动态模式未限制 | 设置图集上限,启用Multi Atlas,定期清理 |
| 文字边缘有黑边 | SDF材质参数 | 调整Sharpness,检查Alpha Cutoff |
避坑技巧:每次修改字体资源后,记得在场景里实际跑一下看看效果。我遇到过在Editor里显示正常,打包到真机上就糊了的情况,原因是不同平台的纹理压缩格式不一样。所以关键节点一定要真机验证。
5. 进阶优化与多语言扩展
5.1 字体图集的压缩与平台适配
烘焙出来的图集默认是RGBA32格式,占用的内存比较大。可以在Font Asset的Inspector里把纹理格式改成Alpha8,因为SDF图集实际上只需要一个通道的数据。这样内存占用直接降到四分之一。
不同平台的纹理压缩格式也需要留意。Android上常用ETC2,iOS上用ASTC,PC上可以用DXT5。在Font Asset的Inspector里可以针对每个平台单独设置压缩格式。注意SDF图集对压缩比较敏感,压缩率太高会导致边缘出现块状伪影,建议用中等压缩率。
5.2 多语言混合排版的处理
如果项目需要同时显示中文、英文、日文、韩文,建议按语言拆分字体资源,然后用Fallback链串起来。比如主字体用英文字体(字形少、图集小),Fallback依次是中文、日文、韩文。这样TMP会优先从主字体查找,找不到再去Fallback里找,既保证了英文的渲染质量,又兼顾了多语言支持。
Fallback链的配置方法:选中主Font Asset,在Inspector的Fallback Font Assets列表里按优先级顺序添加备用字体。TMP会按照列表顺序依次查找。
5.3 运行时动态加载与切换字体
有些项目需要支持用户切换字体,或者根据剧情动态加载不同字体。TMP提供了运行时替换Font Asset的接口:
// 获取TMP组件 TMP_Text textComponent = GetComponent<TMP_Text>(); // 加载新的字体资源 TMP_FontAsset newFont = Resources.Load<TMP_FontAsset>("Fonts/NewFont"); // 替换字体 textComponent.font = newFont; // 如果需要立即刷新显示 textComponent.SetAllDirty();注意替换字体后,之前设置的材质球可能会失效,需要重新指定或者让TMP自动使用新字体的默认材质。
5.4 性能监控与优化建议
TMP的性能开销主要来自两个方面:渲染批次和图集切换。如果同一个界面里用了多个不同的字体资源,TMP会为每个字体资源单独产生一个Draw Call。所以优化方向很明确:尽量减少同屏出现的字体资源数量。
我通常的做法是,整个项目统一使用一套字体资源,通过Fallback来处理不同语言的字符。这样同屏永远只有一个字体资源,Draw Call最少。如果确实需要多种字体(比如标题用一套、正文用一套),也要控制在同一屏内不超过两套。
另外,动态图集的字符生成是在主线程进行的,如果一帧内突然出现大量新字符,可能会造成卡顿。解决办法是提前预热,在Loading界面就把常用字符加载好,避免在游戏过程中集中生成。
6. 个人实操体会与建议
这套TMP中文设置的流程,我从最初踩坑到现在形成固定套路,前后大概经历了十几个项目的迭代。最开始的时候不懂原理,只知道照着教程一步步操作,遇到问题就抓瞎。后来把SDF的原理、图集的生成机制、TMP的查找逻辑都摸清楚了,再遇到问题就能快速定位。
几个我觉得最值得记住的点:字符集一定要按需裁剪,不要贪多求全,图集大小直接关系到包体和内存;静态和动态结合使用,常用字静态烘焙保证性能,生僻字动态补充保证完整;每次修改都要真机验证,Editor和真机的表现可能不一样,尤其是纹理压缩这块。
还有一个容易被忽略的细节:字体资源的命名要规范。我见过项目里几十个字体资源文件,名字全是“Font Asset 1”、“Font Asset 2”这种,过两个月自己都忘了哪个是哪个。建议用“语言_字体名_用途”的格式来命名,比如“CN_SourceHanSans_UI”、“EN_Roboto_Title”,一目了然。
最后分享一个小技巧:如果你不确定某个字有没有被包含在图集里,可以在Editor里选中Font Asset,在Inspector的Character Table里搜索这个字。如果搜不到,说明需要补充烘焙。这个功能在排查缺字问题的时候特别好用,比一个个试快多了。