
1. 为什么TextMeshPro显示中文会成为一个“坑”如果你刚开始用Unity做项目大概率会经历这样一个场景UI界面用Text组件显示英文一切正常换成中文就变成了一堆方块或者问号。这时候你上网一搜发现大家都在推荐TextMeshPro说它渲染清晰、性能好、支持富文本。于是你兴冲冲地导入TMP创建了一个TextMeshPro - Text组件输入中文结果——还是方块。这不是你操作有问题而是TextMeshPro的默认字体资源根本不包含中文字形。TMP和Unity内置的Text组件在字体处理机制上走的是完全不同的路线。内置Text依赖的是Unity的Font资源系统层面会自动回退到可用的中文字体而TMP使用的是预烘焙的Font Asset字体图集里没有的字形就是没有它不会自动帮你去找系统字体。这个差异带来的直接后果就是你必须自己制作一个包含中文字形的TMP Font Asset。而中文字符集动辄两三万字全量烘焙出来的图集大小和内存占用都相当可观。所以这件事的核心矛盾在于——既要让中文正常显示又要控制图集体积和运行时开销。后面我会围绕这个矛盾把整个流程拆开讲清楚。这篇文章适合两类人一是刚接触TMP、被中文显示问题卡住的新手二是已经能用TMP显示中文但想进一步优化图集大小和加载策略的开发者。我会从字体资源准备、Font Asset生成、动态图集配置、常见异常排查几个维度展开尽量把每个步骤背后的原因也讲明白。2. 中文字体资源的准备与授权注意事项2.1 选什么字体文件TMP支持的字体格式是TrueType.ttf和OpenType.otf。理论上任何包含中文字形的字体文件都可以用但实际选择时需要关注几个点。第一是字符覆盖范围。有些字体标称支持中文但实际只覆盖了GB2312的6763个常用字遇到生僻字或者繁体字就会缺字形。如果你的项目面向的是通用场景建议选择覆盖GB18030或者Unicode CJK统一表意文字基本区的字体。第二是字重和风格。TMP的Font Asset是基于某个具体字重生成的Regular和Bold是两个独立的Font Asset不能指望一个Regular通过加粗标签就变成真正的粗体——TMP的b标签只是模拟加粗效果远不如直接用Bold字重生成一个Font Asset。第三是授权问题。这是很多人容易忽略的。商业字体用于商业项目需要购买授权开源字体如思源黑体、思源宋体、文泉驿系列、站酷系列等可以免费商用但也要看清楚具体的许可证条款。我个人的建议是项目初期就用开源字体把流程跑通等美术风格确定后再考虑是否替换为商业字体。2.2 字体文件的导入设置把.ttf或.otf文件拖进Unity的Assets目录后选中它在Inspector面板里需要注意几个设置。Font Size这个参数对TMP来说其实不太重要因为TMP生成Font Asset时会用自己的采样尺寸。但Character选项建议设为Dynamic这样Unity会保留完整的字符信息。Include Font Data必须勾选否则打包后字体数据不会被打进包体运行时就会出问题。还有一个容易踩的坑如果你的字体文件很大比如某些全字符集字体有十几MBUnity导入时可能会卡一下这是正常的。导入完成后在Project窗口里展开这个字体资源应该能看到它下面自动生成了一个Font Asset和一个Material。但注意这个自动生成的Font Asset是Unity内置Text系统用的不是TMP用的。TMP需要你自己通过Font Asset Creator来生成。3. 用Font Asset Creator生成中文Font Asset的完整流程3.1 打开Font Asset Creator的正确方式在Unity菜单栏选择Window TextMeshPro Font Asset Creator。如果你没看到这个菜单说明TMP Essential Resources还没导入。在Window TextMeshPro Import TMP Essential Resources导入一次即可。打开后的面板里Source Font File选择你刚才导入的字体文件。接下来几个参数是决定成败的关键。3.2 采样尺寸与图集分辨率的选择逻辑Sampling Point Size决定了每个字形被采样的精度。这个值越大字形边缘越清晰但占用的图集空间也越大。对于中文来说常用字号在24到48之间。我的经验是如果UI上中文最大显示到72px采样尺寸设48就够了如果要做大标题可以设到64或72。不建议无脑设很大因为图集空间是有限的。Atlas Resolution是图集的分辨率常见选项有512、1024、2048、4096。这里有个计算公式可以帮你估算假设采样尺寸为S每个字符平均占用的像素面积大约是(S2)^2加2是padding那么4096×4096的图集理论上能容纳约(4096×4096)/(S×S)个字符。以S48为例大约是7281个字符。但实际因为排列损耗和padding能放下的会更少。所以如果你要烘焙全量中文字符约两万多字4096的图集在48采样尺寸下是放不下的。这时候就需要用到Character Set的筛选功能。3.3 Character Set的三种策略与适用场景Font Asset Creator提供了几种Character Set选项对应不同的使用策略。ASCII只包含英文字符显然不够用。Custom Range可以手动指定Unicode区间比如4E00-9FFF就是CJK统一表意文字的基本区覆盖了绝大多数常用汉字。Custom Characters可以粘贴一段具体的文本只烘焙这段文本里出现的字符。Characters from File可以指定一个文本文件把文件里所有出现过的字符都烘焙进去。这几种策略怎么选我的建议是分场景如果项目是固定文案的UI比如设置界面、菜单、按钮文字用Characters from File最划算。把所有UI文案整理成一个txt文件烘焙出来的图集可能只有几百KB。如果项目有玩家输入或动态文本比如聊天、命名、随机生成的内容那就需要覆盖常用字范围。可以用Custom Range指定4E00-9FFF但要注意图集大小。如果项目是多语言的建议按语言拆分Font Asset中文一个、日文一个、韩文一个运行时根据语言切换而不是把所有字符塞进一个图集。3.4 渲染模式与图集模式的选择Render Mode有SDF、SDFAA、Raster、Smooth等选项。做UI文字用SDFAASigned Distance Field Anti-Aliasing是标准选择它在各种缩放比例下都能保持清晰。Raster是点阵模式放大后会模糊一般不推荐。Atlas Population Mode有Static和Dynamic两个选项。Static就是烘焙时确定所有字形运行时不能添加新字符。Dynamic允许运行时动态添加字形到图集但需要字体文件本身包含完整的字符数据且图集需要有剩余空间。这里有个重要的取舍Static模式性能最好但字符集固定Dynamic模式灵活但运行时会有图集扩容和重排的开销。对于中文项目我通常建议用Static模式配合合理的字符集筛选因为中文动态添加字形的开销比英文大得多而且图集很容易被撑满。3.5 生成与验证点击Generate Font Atlas按钮后面板下方会显示生成的图集预览。如果看到密密麻麻的汉字排列在图集里说明生成成功。点击Save as...保存为一个新的Font Asset文件。生成完成后把TMP组件上的Font Asset替换成你刚生成的这个。输入中文测试应该就能正常显示了。如果还是方块检查一下是不是Font Asset没有正确赋值或者字符确实不在烘焙范围内。4. 动态图集模式下的中文显示配置与性能权衡4.1 Dynamic模式到底解决了什么问题Static模式最大的限制是字符集固定。如果你的项目允许玩家输入中文名字或者有从服务器下发的动态文案Static模式就无能为力了。Dynamic模式允许TMP在运行时把缺失的字形实时添加到图集中相当于把烘焙过程从编辑器搬到了运行时。但Dynamic模式对中文字体来说有个前提字体文件必须包含完整的字符数据且Unity导入时Character选项要设为Dynamic。如果字体文件本身是子集化的只包含部分字符Dynamic模式也变不出缺失的字形。4.2 动态图集的扩容机制与内存代价Dynamic模式下TMP的Font Asset会维护一个图集纹理。当新字形加入时如果当前图集还有空位就直接写入如果满了TMP会尝试扩容图集。扩容的方式取决于Font Asset的Atlas Width和Atlas Height设置以及Multi Atlas Textures选项。如果开启了Multi Atlas TexturesTMP会创建额外的图集纹理来容纳新字形。每个额外的图集都会占用显存而且图集切换会带来额外的Draw Call。如果不开启图集满了之后就无法再添加新字形缺失的字符会显示为方块。这里有个实际测试的数据可以参考一个4096×4096的SDF图集在采样尺寸48、padding为5的情况下大约能容纳3000到4000个中文字形。如果玩家输入的名字涉及生僻字很容易触发扩容。所以Dynamic模式虽然灵活但内存占用和性能表现是不确定的取决于运行时实际用到了多少字符。4.3 混合策略Static打底 Dynamic补充我在实际项目中用得最多的是一种混合策略用Static模式烘焙一份常用字比如GB2312的6763个字保证绝大多数UI文案和常见输入都能覆盖同时把Font Asset的Atlas Population Mode设为Dynamic并开启Multi Atlas Textures让生僻字有地方可去。这样做的理由是常用字的字形已经在主图集里了不需要运行时动态添加性能稳定生僻字虽然会触发动态添加但频率极低对整体性能影响可以忽略。而且主图集的大小是可控的不会因为动态添加而无限膨胀。配置时需要注意Static烘焙的字符集和Dynamic的补充是共存在同一个Font Asset里的。你可以在Font Asset Creator里先用Custom Range烘焙常用字保存后再把Atlas Population Mode改为Dynamic。这样生成的Font Asset既有预烘焙的字形又支持运行时扩展。5. 中文显示异常时的排查链路与修复方案5.1 方块、问号、乱码的区分与对应原因中文显示异常的表现形式不同原因也不同。方块通常意味着字形缺失TMP找不到对应的字符用缺省方块代替。问号可能是字体文件本身的问题或者字符编码不对。乱码则可能是文本编码问题比如UTF-8和GBK混用。排查时第一步是确认Font Asset里到底有没有这个字符。在TMP组件的Inspector面板底部有一个Font Asset的预览区域点击后可以查看当前Font Asset包含的所有字符。如果目标字符不在列表里那就是烘焙范围没覆盖到。5.2 字体图集溢出与字符丢失有时候Font Asset里明明有某个字符但显示出来还是方块。这种情况可能是图集溢出了。在Font Asset Creator生成时如果字符太多放不下TMP会提示Atlas is full但生成的图集里可能只包含了一部分字符。这时候需要减小采样尺寸、增大图集分辨率或者缩小字符集范围。另一个可能的原因是Material Preset的问题。TMP的Font Asset关联着一个Material如果Material的Shader参数被改错了比如Face Color设成了透明文字就会看不见。检查一下Material的Face和Outline设置是否正常。5.3 动态模式下字符添加失败的常见原因Dynamic模式下如果新字符添加失败首先检查字体文件的Character设置是不是Dynamic。如果字体文件导入时设成了StaticUnity只会保留字体文件里实际用到的字符数据Dynamic模式就找不到其他字形了。其次检查图集是否已满且没有开启Multi Atlas Textures。如果图集满了又没有额外图集新字符就加不进去。可以在运行时通过TMP_FontAsset.TryAddCharacters方法手动添加字符并检查返回值确认添加是否成功。还有一个容易被忽略的点Dynamic模式下的字体文件必须保留在包体里。如果字体文件被标记为Editor Only或者在打包时被剥离了运行时就没有字形数据可用了。确保字体文件的Include Font Data是勾选的并且在打包后的资源里确实存在。6. 中文Font Asset的体积优化与打包策略6.1 图集分辨率与采样尺寸的平衡中文Font Asset的体积主要取决于图集的分辨率和数量。一个4096×4096的SDF图集如果格式是Alpha8大约占16MB如果是RGBA32就是64MB。这个体积在移动端是不可接受的。优化的第一个方向是降低图集分辨率。如果UI上中文最大只显示到48px采样尺寸设32甚至24就够用了。采样尺寸降低后同样分辨率的图集能容纳更多字符或者可以用更小的图集容纳同样多的字符。第二个方向是压缩图集格式。TMP的Font Asset在导入设置里可以调整纹理压缩格式。对于SDF图集Alpha8格式通常就够了因为SDF只需要一个通道。如果平台支持可以用ASTC或ETC2压缩进一步减小体积。但要注意压缩可能会引入伪影需要在真机上验证效果。6.2 按需拆分Font Asset如果项目的中文文案量很大一个Font Asset装不下可以考虑按模块拆分。比如UI用一个Font Asset剧情文本用另一个战斗飘字用第三个。每个Font Asset只包含该模块实际用到的字符总体积反而比一个大而全的图集小。拆分的另一个好处是加载策略更灵活。UI的Font Asset可以常驻内存剧情文本的Font Asset可以在进入剧情时加载、退出时卸载。这样峰值内存占用会低很多。6.3 打包时的资源依赖检查最后一步是打包后的验证。在Editor里显示正常不代表打包后也正常。常见的问题包括字体文件没有被打进包体、Font Asset的Material丢失、图集纹理被压缩得太过导致模糊。建议在打包后跑一次完整的中文显示测试覆盖所有使用中文的界面和动态文本场景。如果发现某个界面的中文变成了方块优先检查该界面使用的Font Asset是否被正确加载以及字符是否在烘焙范围内。另外如果项目使用了AssetBundleFont Asset和它依赖的字体文件、Material、Texture需要放在同一个Bundle里或者确保依赖关系正确否则运行时会出现资源丢失。7. 我在实际项目中积累的几个实用技巧第一个技巧是关于字符集文件的生成。如果你不确定项目到底用了哪些中文字符可以写一个编辑器脚本遍历所有Prefab和场景里的TMP组件把它们的text属性收集起来去重后输出成一个txt文件。用这个文件作为Characters from File的输入烘焙出来的图集就是精确匹配项目实际需求的体积最小。第二个技巧是关于Font Asset的命名规范。建议把字体名称、字重、采样尺寸、字符集范围都体现在文件名里比如SourceHanSans-Regular_S48_GB2312。这样当项目里有多个Font Asset时一眼就能看出区别不会搞混。第三个技巧是关于动态字形的预热。如果项目知道玩家可能会输入哪些字符比如常见的姓氏可以在加载界面提前调用TryAddCharacters把这些字符预热到图集里避免游戏过程中突然卡顿。预热列表可以从常用姓氏表或项目历史数据里提取。第四个技巧是关于SDF Scale的调整。TMP的Material里有一个Scale参数影响SDF的渲染效果。如果发现中文边缘有硬边或者模糊可以微调这个值。一般来说采样尺寸越大Scale可以设得越小采样尺寸小的时候Scale要适当增大来补偿。第五个技巧是关于多语言切换时的Font Asset管理。如果项目支持中英日韩多语言建议用一个Font Asset Manager来统一管理不同语言的Font Asset加载和切换。切换语言时遍历所有TMP组件把Font Asset替换成对应语言的同时注意Material Preset也要跟着换否则描边、阴影等效果可能会丢失。