免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Unity UI文本优化:从UGUI Text到TextMeshPro迁移与性能调优指南

Unity UI文本优化:从UGUI Text到TextMeshPro迁移与性能调优指南 做了这么多年Unity UI我对文本控件一直又爱又恨。爱是因为任何一个界面都离不开它恨是因为它遇到问题的时候特别隐蔽——要么中文渲染出来是方块要么字体发虚要么稍微一改文本就掉帧排查起来极为折磨。这篇我专门围绕UGUI下的Text与TextMeshProTMP展开把控件选型、关键属性、字体资产创建、渲染原理和优化技巧全部串起来讲一遍适合Unity初学者、UI开发以及正在从旧版Text迁移到TMP的团队参考。1. Text 与 TMP 的核心差异为什么我劝你尽早切换1.1 Legacy Text 的本质与局限在Unity里提到“Text”有可能会指两个东西一个是UnityEngine.UI.Text另一个是TextMeshPro也就是TMP。前者是UGUI诞生时就有的默认文本组件后者原本是第三方插件后来被Unity收购并内置到引擎现在官方已经把TMP作为推荐方案甚至在新项目里默认菜单项都已经用TMP代替旧的Text。旧Text的工作机制其实很简单它依赖一个动态字体图集运行时把字符串里的每个字符映射到图集上的UV坐标然后通过CanvasRenderer生成一组四边形网格顶点交给Canvas渲染。听起来没毛病但它有几个非常致命的短板动态字体图集是全局共享的一旦遇到图集里没有的字符就要重新构建图集这个过程中会有一帧明显的卡顿。中文项目尤其容易触发因为中文字符数量动辄几千。想要给文字加描边或者阴影传统做法是挂Outline和Shadow组件这两个组件本质上是复制和偏移网格顶点一个文字要画好几遍合批和填充率都会受影响。文字在放大缩小时会发虚尤其是把Canvas的缩放、各种相机缩放混在一起时效果并不理想。排版能力很弱行距、字距、缩进、上下标这些要么不支持要么实现得非常粗糙。我见过不少团队早期图方便全部用旧Text结果到了剧情对话、聊天系统、计分板这种高频更新文本的场景时性能问题集中爆发。最后还是要换到TMP代价是全局搜索替换组件、重新适配字体资产工程量一点也不小。1.2 TMP 的核心设计SDF 渲染管线TMP之所以能解决这些痛点核心在于它采用了一种完全不同的渲染技术SDF即有向距离场。SDF的思路是先把字体渲染成一张特殊的图集图中每个像素存的不是颜色而是这个像素到最近字形的有向距离。简单理解就是每个像素都在记录“我离字体边缘有多远”距离的符号表示是在字形里面还是外面。然后在渲染时GPU在片元着色器里通过smoothstep对距离值进行阈值判断把连续的边缘“切”出来。这个方案带来几个显著优势字体在任何缩放比例下都能保持平滑不会像位图字体那样放大后出现锯齿。描边、阴影、发光、外发光等效果不需要复制网格直接在材质层通过修改SDF计算方式实现性能开销小得多。图集可以复用一套字体资产从12号到120号都能保持清晰。当然SDF也有自己的代价。一是字体图集生成时间比普通动态字体长二是对于超大字号的渲染SDF的采样精度可能不够需要设置更高的采样点数比如2048甚至4096。1.3 两者差异速查表用一张表概括常见对比维度方便你在技术选型或团队汇报时直接参考对比维度Legacy TextTextMeshPro (TMP)渲染技术位图动态图集SDF有向距离场缩放清晰度大字号或缩放后发虚任意缩放保持平滑描边/阴影额外组件复制网格材质层直接设置富文本支持部分标签能力弱支持成套标签体系中文字体支持动态图集容易卡顿字体资产回退机制完善排版控制行距字距有限字距、词距、行距、段落缩进均可调性能开销图集重建卡顿明显图集可控、网格生成更高效官方推荐已接近废弃正式推荐方案TMP在功能上几乎完全覆盖旧Text再加上官方持续的更新驱动没有理由继续抱残守缺。如果项目允许尽量在新UI界面里直接把TMP作为默认文本组件。2. Text(TMP) 组件属性逐项拆解2.1 基础文本区Text、Font Asset、Font Style、Font Size如果你已经挂上了TMP_Text组件第一眼看到的是一个大大的输入框这里就直接输入显示内容。输入框支持富文本标签比如想在某个词上加颜色可以直接写color#FF0000红色/color。注意富文本标签需要在组件的Rich Text开关打开时才生效。字体资产这一栏也就是Font Asset是TMP最重要的概念之一。它和旧Text直接拖一个TTF字体文件进去完全不同。TMP的字体资产是一个.asset资源内部包含了SDF图集、字符表、字形度量信息、材质预设等。你拖一个TTF进去TMP并不能直接用必须先在Font Asset Creator里把TTF生成成TMP字体资产。后面第三章我会专门讲。字体样式Font Style这里提供常规、粗体、斜体、下划线、删除线等选项。对应富文本标签是b、i、u、s。需要提醒的是通过样式做的加粗并不是真的换了一套粗体字而是基于SDF的模拟加粗在超小字号下可能会糊。如果真的想要正规粗体效果更推荐的做法是换字体资产或者用字体文件中自带的粗体字形集。字号Font Size在TMP里默认单位是point但实际显示大小还受Canvas缩放影响。如果你直接填32在Canvas Scale Mode为Scale With Screen Size的UI里最终像素大小是由Canvas的reference resolution和实际屏幕分辨率共同决定的。这也是很多新手困惑的地方为什么我设了字号32在不同分辨率下看起来大小不一样。这是Canvas缩放机制决定的不是TMP的问题。2.2 视觉效果区Vertex Color、Color Gradient、Material PresetTMP提供两个颜色入口Vertex Color和Color Gradient。前者是给整个文本统一染色后者可以做渐变色。渐变支持四个角的独立颜色能做出从上到下、从左到右甚至四角各不相同的渐变效果。Material Preset比较特殊。你可以为同一个字体资产创建多个材质预设比如一个普通白色一个带描边一个带发光然后用不同材质预设让同一份文本渲染出完全不同的视觉效果。这样做最大的好处是你可以在不同UI元素上复用同一套字体图集却使用不同材质兼顾了效果和灵活性。但要注意材质预设如果创建太多每个不同材质都可能打断UGUI的合批。同一个Canvas下使用不同材质的TMP文本会让批处理拆分Draw Call数量增加。所以我的经验是材质预设控制在2-3套以内尽量让同屏文本共用同一个材质。2.3 排版布局区Alignment、Wrapping、OverflowTMP的对齐分为水平对齐和垂直对齐水平有Left、Center、Right、Justified垂直有Top、Middle、Bottom另外还有Geometry和Caps两个基准模式。Geometry对齐是基于字形的几何高度来对齐Caps是基于大写字母高度来对齐。如果你的文本里混有中文、英文、数字通常用Geometry模式看起来更稳因为中文的高度和大写字母高度不一致用Caps模式容易出现视觉偏移。Wrapping开关控制是否自动换行。关闭后文本超出矩形区域会直接往外延伸这在制作滚动字幕或特殊布局时有用但在正常情况下建议开启。换行时TMP会尽量在任何字符间距处断开不特定用空格作为断词点这对中文项目其实很友好因为每个汉字都是一个独立的“词”不会出现英文单词被截断的问题。Overflow有5种模式Overflow、Truncate、Ellipsis、Masking、ScrollRect。Overflow文本超出部分照常渲染超出矩形框也不裁剪适合不需要边界约束的场景。Truncate超出部分直接裁掉不显示省略号。Ellipsis超出部分用省略号代替适合列表标题、按钮文本等需要单行省略的场景。Masking用RectTransform的边界做遮罩超出部分被裁掉同时保持可编辑。ScrollRect配合ScrollRect/ScrollView使用文本被Mask裁剪并支持滚动。我用得最多的是Ellipsis但要注意它的性能和准确性问题。TMP为了确定省略号该放在哪里需要反复测量每个字符的宽度这个测量过程在文本特别长时会带来额外开销。如果只是静态列表里的单行标题问题不大但如果是频繁更新的聊天框或者长文本就别用Ellipsis改用Overflow配合外层Mask裁剪会更稳。2.4 交互与高级项Raycast Target、Margins、Auto SizeRaycast Target是UGUI所有Graphic组件都有的开关。它的作用是决定这个组件是否参与UI射线检测。对于纯展示型文本比如提示文字、版本号、背景标题一定记得关掉。不然鼠标点击会穿透到这些文本上挡住后方的按钮。实际项目中我会在搭建UI时就把所有纯文本的Raycast Target关闭只在确实需要响应的文本上打开。Margins是文本内容和矩形边缘之间的距离。注意它控制的是TMP内容布局时相对RectTransform的缩进而不是裁剪范围。在做按钮文本时如果文字总是贴边可以用这个属性把文字往内缩。Auto Size就是旧Text里的Best Fit让文本根据RectTransform大小自动缩放字号直到填满或者缩到最小限制。TMP在这里提供了最小和最大字号限制很实用。但我个人建议不要对大量文本开Auto Size因为自动缩放需要实时测量和计算频繁触发会导致Layout和Canvas重建Unity官方也建议尽量用固定字号Auto Size只用于个别需要自适应的标题。3. 从零创建 TMP 字体资产中文项目必看3.1 为什么非要手动创建字体资产Unity里TMP默认自带的字体是LiberationSans SDF这套字体只覆盖拉丁字符集基本不能显示中文。所以中文项目第一步肯定是创建一个支持中文的TMP字体资产。这一步也是很多新手卡住的地方明明我导入了思源黑体为什么拖到Text组件上还是方块原因是TMP字体资产不是TTF文件本身而是基于TTF生成的一个新资源。你要打开Window TextMeshPro Font Asset Creator在Source Font File里选TTF/OTF然后设置参数、点击Generate Font Atlas最后Save成一个字体资产文件。生成的.asset文件才是TMP能识别的字体资源。3.2 创建流程与关键参数常用设置如下Source Font File选择你导入的中文字体比如思源黑体、阿里巴巴普惠体、站酷字体等。Sampling Point Size决定SDF图集的采样大小。纯英文1024足够中文字符多建议至少2048如果字体极大或者要求极高清晰度可以上4096。Padding控制字符之间的间距和边缘余量一般5到9即可。Padding太小描边或者大号字容易出现边缘被截断。Packing Method选Optimum它会尽量把字符紧凑排列节省图集空间。Fast打包速度快但图集利用率低。Atlas Resolution默认等于Sampling Point Size保持默认即可。Character Set选择Custom Range或Custom Characters决定哪些字符会被生成进图集。Render Mode默认SDFAA就行这个组合通常是性能和效果最均衡的选择。生成后把字体资产拖到TMP Text组件的Font Asset一栏即可。顺便建议生成的字体资产放在一个专门的FontAssets文件夹下按字体名和大小命名方便后续多个界面复用。3.3 动态字体资产与静态字体子集的取舍TMP的字体资产有两种图集模式静态和动态。静态图集表示生成时就确定了字符集运行时不会新增字符。好处是可预测、性能稳定缺点是如果运行时遇到图集之外的字符就显示不出或者变成方块只能提前准备好字符集。动态图集则表示在运行期间遇到图集没有的字符时会自动从源字体中提取并插入图集。听起来很方便但代价是动态图集扩展时会造成CPU开销和寻址扩展的停顿频繁新增不常见字符时卡顿会很明显。我的建议是对于UI上通用文本使用覆盖常用3500汉字和标点的静态图集对于用户输入昵称、聊天、服务器下发的不固定文本用动态字体资产并把动态图集Size限制在可接受范围内。这里可以再使用TMP的Fallback机制也就是设置主字体资产为常用字符的静态资产遇到生僻字时自动回退到动态字体资产兼顾性能和覆盖度。3.4 中文字体子集的生成技巧全量中文字符集有数万个如果全部生成到字体图集图集可能撑到4096以上都放不下而且降低加载速度。实际项目中常用汉字也就3500个左右加上标点符号和数字英文4000个字符基本覆盖绝大多数UI场景。我通常用Python的fontTools库从TTF里直接提取一个自定义子集先用脚本读入一个常用字表再用TTFont过滤出需要的字形导出精简后的TTF最后再用这个精简TTF生成TMP字体资产。这样生成的图集很小加载很快。到游戏后期如果发现有生僻字没覆盖再开一个动态回退字体资产专门兜底。静态主资产保证日常渲染性能动态兜底保证不出现方块字是我做过十几个项目里最稳的方案。4. UGUI 渲染原理与 TMP 的性能优化4.1 UGUI 的渲染管线Canvas 与 Mesh 重建要理解TMP的性能表现得先从UGUI的渲染管线说起。当Unity UI界面上的元素发生变化时Canvas会进入“Rebuild”流程。Unity的CanvasUpdateRegistry会收集所有需要更新的Graphic比如Image、Text、RawImage驱动它们重新生成网格数据。这些网格数据会被传给CanvasRenderer最终被合批并提交给GPU。旧Text和TMP在Rebuild时都要重新生成TextMesh的顶点数据和UV。不同点是旧Text每次改字符串都去查动态字体图集检查字符是否在图集里不在就触发图集重建。TMP改字符串时走的是一套自己的字符映射和排版测量流程并把结果写入顶点缓冲区。无论哪种频繁修改文本都会触发Canvas Rebuild这是UI开销的重要来源。所以优化的大方向是减少无效Rebuild压缩Rebuild成本。4.2 TMP 的字体图集机制与显存占用TMP生成的SDF字体图集本质上是一张Texture2D。图集大小由Atlas Width和Atlas Height决定常见2048x2048、4096x4096。每个字符在生成时都会被“烘”进这张图集里再配合一个材质引用它。图集越大显存占用越高。多个字体资产就是多张图集会占用大量显存所以别动不动就给每种字体都生成一个4096图集尽量复用。TMP的动态字体资产在扩展时并不是简单在图集里加一个格子它可能需要重新排列已有字符甚至迁移整张图集这个过程会造成明显的CPU峰值。所以高频更新的文本字段比如每帧更新的分数、血量数字一定要用静态字体资产且字符预先覆盖所有可能出现的数字和字母。4.3 文本更新的正确姿势SetText 与 StringBuilder实际开发中最常见的问题写法是这样scoreText.text Score: score;字符串拼接会产生大量临时对象GC压力大同时每次赋值都触发TMP的完整解析流程。更推荐的做法是使用StringBuilder配合SetText方法using System.Text; using TMPro; using UnityEngine; public class ScoreDisplay : MonoBehaviour { [SerializeField] private TMP_Text scoreText; private StringBuilder sb new StringBuilder(32); private int currentScore; public void AddScore(int value) { currentScore value; sb.Clear(); sb.Append(Score: ); sb.Append(currentScore); scoreText.SetText(sb); } }SetText接收StringBuilder和接收字符串走的是不同的重载前者可以避免额外分配尤其适合每帧都要刷新的文本。另一个减少Rebuild的技巧是尽量把静态文本和动态文本拆到不同Canvas下。同一个Canvas里只要有任何元素触发Rebuild整个Canvas都可能要重新合批拆开后动态区域的Rebuild不会影响静态区域。这个策略在游戏主界面、战斗HUD上特别有效。4.4 材质合并与批处理优化UGUI的合批依赖材质和贴图是否一致。TMP每个字体资产对应一个材质如果同一界面里有的TMP用普通材质有的用描边材质两个材质不同合批就被打断Draw Call增加。如果想在同一个字体资产上使用多种外观建议在材质预设里创建预设并尽量把同材质预设的TMP文本放在相邻节点便于UGUI批量合并。同时对不需要交互的文本关闭Raycast Target减少CanvasRebuild阶段额外的射线检测判断。5. 常见问题与排查技巧实录5.1 高频问题排查表下面这些问题基本覆盖了我这几年被问到最多的情况直接对照排查现象可能原因解决方案中文显示为方框或空白字体资产字符集不包含中文或者没有Fallback字体使用覆盖常用中文的静态字体资产设置Fallback新建的字体资产显示粉色生成的材质资源丢失或Shader不可用重新生成字体资产检查TMP Essentials包安装是否完整TMP文本放大后模糊SDF采样点不足字号与采样不匹配重新生成字体资产调大Sampling Point Size到2048或4096修改文本明显卡顿动态字体图集扩展或整个Canvas频繁Rebuild优先使用静态字体资产文本更新拆到独立Canvas用SetText优化富文本标签不生效Rich Text未打开或标签不闭合或使用了旧版Text组件确认组件是TMP开启Rich Text检查标签闭合Ellipsis省略号位置不准字体度量数据或边距不合理调整Margins或改用Masking/Overflow模式TMP文本和图片无法合批材质不同或字体资产不同统一材质预设将同材质文本相邻排列按钮点击被文字挡住Raycast Target默认开启关闭纯展示文本的Raycast Target文本在Canvas缩放后位置漂移对齐方式与RectTransform锚点配合不当统一锚点和对齐基准优先用Geometry对齐5.2 几个容易踩坑的细节第一个坑是TMP字体资产里的Atlas Population Mode。默认是Static如果你在生成图标集时不注意把所有中文字符都没有包含进去运行后补字符是补不进去的。很多项目到后期突然出现个别生僻字显示为方块就是因为生僻字不再支持动态插入。第二个坑是继承和预制体覆盖。当一个TMP文本做了颜色或字体覆盖后后面对Prefab批量修改字体资产子预制体里的引用可能还是旧资产视觉上看起来就是某个按钮的字体不一样。批量替换时最好写一个编辑器脚本统一处理而不是手动逐个点。第三个坑是SDF图集的分辨率和内存。初学者容易盲目把图集开成4096觉得越大越清晰实际上如果字体本身字号很小4096图集只会浪费显存。正确做法是先估算项目的字符集数量再根据字符集大小决定图集分辨率。3500常用字用2048通常够用超大字号或者精细描边才考虑跳到4096。5.3 如果界面里混用旧Text和TMP怎么办老项目迁移时很可能还留着大量旧的Text组件。这里我的经验是分步走先把核心界面、高频更新的文本全部切到TMP列表项、弹窗这类次要界面陆续替换。GameObject UI Legacy菜单里还能找到旧Text说明Unity还没完全移除它但官方更新已经对它很冷淡了。早点迁移越到后期成本越低。如果你在迁移过程中遇到TMP字体和旧Text字体显示效果不一致比如旧Text用的是黑体TMP换了字体资产后粗细不同那就调整TMP的Font Weight或Face Color或者干脆在项目里统一定义一套UI字体规范所有界面都指向同一个字体资产和同一套材质预设。结尾一点个人体会做UI文本优化这几年我最深的感受是“细节决定成败”。TMP虽然功能强大但它的强大也意味着更多的配置项和更复杂的决策点。每个属性的选择背后都关系到性能、视觉效果和后续维护成本。我个人习惯是在项目启动阶段就统一好字体资产和材质预设规范把Text组件全部默认成TMP把Raycast Target默认关掉把常用中文字符集预生成好这样后期基本不会出大问题。如果你现在正被中文方块、文本卡顿或者材质粉屏困扰可以按照上面章节的顺序一步步排查。尤其是第3章的字体资产创建和第五章的问题表是我觉得最值得收藏的部分。等把TMP这套机制摸透了你会发现在Unity里做UI文本其实是件很顺手的事。
返回列表