🎨 Unity NGUI 与 UGUI 性能优化实战:双框架优化策略完全指南
💡 双框架优化的价值:
- 项目同时使用 NGUI 和 UGUI,优化策略不同?
- NGUI 的 UIPanel 和 UGUI 的 Canvas 有什么区别?
- 如何针对不同框架制定优化方案?
- DrawCall、网格重建、OverDraw 如何全面优化?
这篇文章! 将全面对比 NGUI 和 UGUI 优化策略,从机制差异到实战技巧,让 UI 性能全面提升!
一、UI 优化概览
1.1 三大优化目标
| 目标 |
说明 |
影响 |
| 降低渲染开销 |
减少 DrawCall,控制 OverDraw |
GPU 性能 |
| 降低更新开销 |
控制网格重建,避免全量更新 |
CPU 性能 |
| 高效处理大量 HUD |
优化血条、飘字等高频元素 |
整体性能 |
1.2 NGUI 与 UGUI 对比
| 特性 |
NGUI |
UGUI |
| 元素更新 |
SetActive 触发重建 |
RectTransform 变更触发 |
| DrawCall 合并 |
UIPanel 为单位 |
Canvas 为单位 |
| 网格更新 |
LateUpdate 中更新 |
Job System 多线程 |
| 检测工具 |
DrawCall Tool |
Frame Debugger |
二、NGUI 优化
2.1 元素更新机制
1 2 3 4 5 6 7 8
| NGUI 元素展示流程: ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ 图片/文字 │──>│ 顶点数据 │──>│ UV数据 │──>│ Mesh │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ ▼ ▼ 颜色、法线、切线 GPU 渲染 三角形顶点索引
|
2.2 DrawCall 合并规则
| 规则 |
说明 |
| 以 UIPanel 为单位 |
不同 Panel 不会合并 |
| 按 Depth 排序 |
Depth 连续的元素可合并 |
| 相同图集 |
必须使用相同图集才能合并 |
| 相同材质 |
材质不一致会分开 DrawCall |
2.3 网格更新机制
⚠️ 重要:FillAllDrawCalls 频繁触发会导致 CPU 峰值!
2.4 NGUI 隐藏元素策略
| 方法 |
触发重建 |
推荐度 |
SetActive(false) |
✅ 是 |
⭐⭐ |
Color.a = 0 |
❌ 否 |
⭐⭐⭐⭐ |
Scale = 0 |
❌ 否 |
⭐⭐⭐⭐ |
| 移除重建 |
✅ 是 |
⭐⭐⭐ |
2.5 UIPanel 优化选项
1 2 3 4 5 6
| Inspector 面板选项: ┌─────────────────────────────────────┐ │ ☑ Static - 静态界面不产生位移 │ │ ☑ Visible - 不计算包围盒 │ │ ☑ UIPanel - 控制 FillAll 调用 │ └─────────────────────────────────────┘
|
| 选项 |
适用场景 |
效果 |
| Static |
静态界面 |
不动态更新网格 |
| Visible |
完全展示的元素 |
跳过包围盒计算 |
2.6 特殊组件处理
| 组件 |
问题 |
解决方案 |
| UITexture |
单独占用 DrawCall |
合入相同图集或分层 |
| 穿插 Depth |
破坏合批 |
设置相近的 Depth |
三、UGUI 优化
3.1 元素更新机制
1 2 3 4 5 6
| gameObject.SetActive(false); RectTransform.position = newPosition;
CanvasGroup.alpha = 0;
|
3.2 DrawCall 合并规则
| 规则 |
说明 |
| Canvas 为单位 |
不同 Canvas 不会合并 |
| 层级顺序 |
图片与图片、文字与文字同级 |
| 相同材质 |
Material 不一致会分开 |
| 不重叠 |
重叠元素会分开 DrawCall |
3.3 网格更新机制
1 2 3 4 5 6 7 8 9 10 11
| UGUI 更新流程: ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Canvas.Send │──>│ WaitingFor │──>│ PutGeometry │ │ MessageInG │ │ JobGroupJob │ │ JobFence │ └─────────────┘ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────┐ │ BatchRender │ │ Flush │ └─────────────┘
|
💡 提示:使用 Frame Debugger 查看 Canvas.BuildBatch 开销。
3.4 DrawCall 上升原因
| 原因 |
说明 |
解决方案 |
| 层级穿插 |
ScrollView 与红点等穿插 |
调整层级顺序 |
| Z 值不为 0 |
与其他 UI 不同 |
统一 Z=0 |
| 动态遮挡 |
运行时重叠检测 |
避免动态遮挡 |
| 不同图集 |
Tag 相同但图集不同 |
检查图集打包设置 |
3.5 Sprite Packer 注意
1 2 3 4 5 6 7 8 9 10 11
| 图集打包注意事项: ┌─────────────────────────────────────┐ │ 同 Tag 纹理被打包到不同图集: │ │ │ │ 原因: │ │ • Alpha 通道不同 │ │ • 压缩方式不同 │ │ • 纹理尺寸差异过大 │ │ │ │ 建议: 统一纹理导入设置 │ └─────────────────────────────────────┘
|
四、通用优化策略
4.1 动静分离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Canvas 分离策略: ┌─────────────────────────────────────┐ │ 主 Canvas (静态) │ │ ┌─────────┐ ┌─────────┐ │ │ │ 背景 │ │ 装饰 │ │ │ └─────────┘ └─────────┘ │ │ │ │ 动态 Canvas (频繁更新) │ │ ┌─────────┐ ┌─────────┐ │ │ │ 血条 │ │ 倒计时 │ │ │ └─────────┘ └─────────┘ │ │ │ │ 特效 Canvas (粒子/特效) │ │ ┌─────────┐ ┌─────────┐ │ │ │ 粒子 │ │ 伤害飘字 │ │ │ └─────────┘ └─────────┘ │ └─────────────────────────────────────┘
|
4.2 降低更新频率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MinimapUpdater : MonoBehaviour { public float updateInterval = 0.5f; private float timer;
void Update() { timer += Time.deltaTime; if (timer >= updateInterval) { UpdateMinimap(); timer = 0; } } }
|
4.3 避免”敏感操作”
| 操作 |
问题 |
优化方案 |
| 频繁 SetActive |
触发全量重建 |
使用 Alpha/Scale |
| 连续 Position 赋值 |
可能无变化 |
检查变化再赋值 |
| 动态添加/删除元素 |
破坏合批 |
使用对象池 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void Update() { rectTransform.position = targetPosition; }
private Vector3 lastPosition; void Update() { if (targetPosition != lastPosition) { rectTransform.position = targetPosition; lastPosition = targetPosition; } }
|
五、HUD 元素优化
5.1 血条优化
| 策略 |
说明 |
| 使用 Simple/Filled 模式 |
减少顶点数 |
| 相同图集 |
确保 DrawCall 合并 |
| 分帧加载 |
避免单帧创建过多 |
| 替换为 Shadow |
避免世界坐标转换 |
1 2 3 4 5 6
|
Canvas hpCanvas = Instantiate(hpCanvasPrefab); hpCanvas.renderMode = RenderMode.WorldSpace; hpCanvas.worldCamera = uiCamera;
|
5.2 伤害飘字优化
| 问题 |
解决方案 |
| 频繁创建销毁 |
使用对象池 |
| 每帧更新位置 |
隔帧更新 |
| 数量爆炸 |
控制同屏总量 |
| 文字转图片 |
使用图片字体 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class DamageTextPool : MonoBehaviour { private Queue<DamageText> pool = new Queue<DamageText>(); public int maxCount = 20;
public DamageText Get() { if (pool.Count > 0) return pool.Dequeue(); return CreateNew(); }
public void Return(DamageText text) { if (pool.Count < maxCount) pool.Enqueue(text); else Destroy(text.gameObject); } }
|
六、OverDraw 优化
6.1 OverDraw 概念
1 2 3 4 5 6 7 8 9 10 11 12
| OverDraw 示意: ┌─────────────────────────────────────┐ │ 填充倍数 = 实际绘制像素 / 屏幕像素 │ │ │ │ 背景 (1x) │ │ ┌─────────┐ │ │ │ 按钮 │ (2x - 重叠1次) │ │ │ ┌───┐ │ │ │ │ │红点│ │ (3x - 重叠2次) │ │ │ └───┘ │ │ │ └─────────┘ │ └─────────────────────────────────────┘
|
6.2 性能参数
| 参数 |
说明 |
建议值 |
| 总填充数值峰 |
单帧总填充像素最大值 |
越低越好 |
| 填充倍数峰值 |
单帧最大填充倍数 |
<3x |
| 单帧填充倍数 |
总填充数/分辨率 |
<2x |
6.3 绘制顺序优化
| 队列 |
绘制顺序 |
适用对象 |
| Geometry |
从前向后 |
不透明 UI |
| Transparent |
从后向前 |
半透明 UI |
| Overlay |
从后向前 |
覆盖层 UI |
💡 提示:移动端尽量使用 Geometry 队列实现从前向后绘制!
6.4 OverDraw 优化策略
| 策略 |
说明 |
| 控制绘制顺序 |
从前向后绘制,利用 Early-Z |
| 减小重叠区域 |
减少不必要的重叠 |
| 镂空绘制 |
只绘制可见区域 |
| 简化 UI 设计 |
减少装饰性重叠元素 |
七、针对性优化
7.1 文字优化
| 问题 |
原因 |
解决方案 |
| Outline 开销大 |
复制4份文字 Mesh |
使用 Shadow 替代 |
| Shadow 性能 |
添加顶点实现阴影 |
使用 TextMeshPro |
7.2 Mask 组件优化
| 组件 |
DrawCall 开销 |
OverDraw |
替代方案 |
| Mask |
+4 DC |
有 |
RectMask2D / MeshMask |
| RectMask2D |
+0 DC |
无 |
仅矩形区域 |
| MeshMask |
+1 DC |
无 |
自定义形状 |
7.3 Image 组件优化
| 设置 |
效果 |
| 取消 Fill Center |
九宫格中心镂空 |
| 禁用 Raycast Target |
不需要交互的元素 |
| 避免 Tiled 类型 |
减少纹理采样 |
| 避免 Pixel Perfect |
减少计算开销 |
7.4 空组件处理
1 2 3 4 5 6 7 8 9 10 11
| <Image raycastTarget="true" />
public class EmptyGraphic : Graphic { protected override void OnPopulateMesh(VertexHelper vh) { } }
|
八、优化检查清单
8.1 快速检查
| 检查项 |
NGUI |
UGUI |
| DrawCall 数量 |
<50 |
<30 |
| 网格重建频率 |
检查 FillAll 调用 |
检查 BuildBatch |
| OverDraw |
<2x 平均 |
<2x 平均 |
| 动静分离 |
✅ |
✅ |
| 对象池 |
✅ |
✅ |
8.2 禁用组件清单
| 组件/功能 |
原因 |
替代方案 |
| UI/Effect/Shadow |
4份 Mesh 顶点 |
TextMeshPro |
| UI/Effect/Outline |
继承 Shadow 更开销 |
TextMeshPro SDF |
| Image.Tiled |
高采样开销 |
自定义 Shader |
| Pixel Perfect |
额外计算 |
手动布局 |
| Text.BestFit |
预生成所有字号 |
固定字号 |
8.3 性能建议
| 场景 |
建议配置 |
| PC 项目 |
DrawCall <100,可使用丰富特效 |
| 手游高端 |
DrawCall <50,适度特效 |
| 手游低端 |
DrawCall <30,最简配置 |
九、参考资料
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com