🎨 Unity UI 优化完全手册:NGUI 与 UGUI 的性能提升之道

💡 UI 优化的价值

  • UI 导致 DrawCall 爆炸,帧率暴跌?
  • Canvas 刷新频率过高,CPU 压力巨大?
  • 血条、飘字等 HUD 元素太多,性能扛不住?
  • NGUI 和 UGUI 优化有什么不同?

这篇文章! 将系统讲解 Unity UI 优化技术,从渲染开销到更新频率,从 NGUI 到 UGUI,让 UI 性能翻倍!


一、UI 优化概览

1.1 三大优化目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UI 优化三大支柱:
┌─────────────────────────────────────┐
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 降低渲染开销 │ │ 降低更新开销 │ │
│ │ DrawCall │ │ 网格重建 │ │
│ │ OverDraw │ │ Layout Rebuild│ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ 高效处理HUD │ │
│ │ 血条/飘字 │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────┘
目标 关键指标 影响
降低渲染开销 DrawCall 数量、OverDraw GPU 性能
降低更新开销 网格重建频率、Layout Rebuild CPU 性能
高效处理 HUD 血条/飘字数量、更新频率 整体性能

1.2 NGUI vs UGUI 对比

特性 NGUI UGUI
元素更新方式 SetActive 触发重建 RectTransform 变更触发
隐藏方式 Alpha=0、Scale=0 CanvasGroup.alpha
DrawCall 合并 UIPanel 为单位 Canvas 为单位
网格更新 LateUpdate 中更新 Job System 多线程
检测工具 DrawCall Tool Frame Debugger

二、降低渲染开销

2.1 DrawCall 优化

检查项 建议值 说明
正常界面 <30 DC 常规功能界面
复杂界面 <50 DC 含大量动画/特效
检测工具 Frame Debugger 分析合批情况

UGUI DrawCall 规则

1
2
3
4
5
6
7
8
9
10
11
12
13
UGUI 合批条件:
┌─────────────────────────────────────┐
│ 必须同时满足: │
│ │
│ 1. 相同 Canvas │
│ 2. 相同图集 (Texture) │
│ 3. 相同材质 (Material) │
│ 4. 层级顺序一致 (无穿插) │
│ 5. 无重叠 (或可忽略重叠) │
│ 6. Z 值一致 (通常为 0) │
│ │
│ 打破任一条件 → 产生新 DrawCall │
└─────────────────────────────────────┘

DrawCall 上升原因

原因 说明 解决方案
层级穿插 ScrollView 与红点等穿插 调整层级顺序
Z 值不为 0 与其他 UI 不同 统一 Z=0
动态遮挡 运行时重叠检测 避免动态遮挡
不同图集 Tag 相同但打包不同 检查 Sprite Packer

2.2 OverDraw 优化

性能参数 定义 建议值
总填充数值峰 单帧总填充像素数量最大值 越低越好
填充倍数峰值 单帧最大填充倍数 <3x
单帧填充倍数 总填充数 / 分辨率 <2x

绘制顺序优化

队列 绘制顺序 适用对象 优化效果
Geometry 从前向后 不透明 UI 利用 Early-Z
Transparent 从后向前 半透明 UI 正确混合
Overlay 从后向前 覆盖层 UI 正确显示

💡 提示:移动端尽量使用 Geometry 队列实现从前向后绘制!

OverDraw 优化策略

策略 说明
控制绘制顺序 从前向后绘制,利用 Early-Z
减小重叠区域 减少不必要的重叠
镂空绘制 只绘制可见区域 (PolygonImage)
简化 UI 设计 减少装饰性重叠元素

三、降低更新开销

3.1 网格更新机制

1
2
3
4
5
6
7
8
9
10
11
UGUI 网格更新流程:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│Canvas.Send │──>│WaitingForJob│──>│PutGeometry │
│MessageInG │ │JobGroup │ │JobFence │
└─────────────┘ └─────────────┘ └─────────────┘


┌─────────────┐
│BatchRenderer│
│ Flush │
└─────────────┘

3.2 性能分析函数

函数 说明 问题定位
Canvas.BuildBatch 合并 Canvas 下所有 UI 元素网格 检查合批开销
Canvas.SendWillRenderCanvases 通知重建 Layout 或 Graphic 检查重建频率
WaitingForJobGroup 等待子线程网格合并 网格开销巨大
PutGeometryJobFence 网格合并同步点 多线程等待

3.3 触发重建的操作

操作 NGUI UGUI
隐藏元素 SetActive(false) 触发 SetActive(false) 触发
替代方案 Alpha=0Scale=0 CanvasGroup.alpha=0
位置变更 可能触发重建 RectTransform 变更触发

3.4 动静分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Canvas 分离策略:
┌─────────────────────────────────────┐
│ 主 Canvas (静态) │
│ ┌─────────┐ ┌─────────┐ │
│ │ 背景 │ │ 固定装饰 │ │
│ └─────────┘ └─────────┘ │
│ │
│ 动态 Canvas (频繁更新) │
│ ┌─────────┐ ┌─────────┐ │
│ │ 血条 │ │ 倒计时 │ │
│ └─────────┘ └─────────┘ │
│ │
│ 特效 Canvas (粒子/特效) │
│ ┌─────────┐ ┌─────────┐ │
│ │ 粒子 │ │ 伤害飘字 │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────────┘

3.5 降低更新频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ✅ 降低小地图更新频率
public class MinimapUpdater : MonoBehaviour
{
public float updateInterval = 0.5f; // 0.5秒更新一次
private float timer;

void Update()
{
timer += Time.deltaTime;
if (timer >= updateInterval)
{
UpdateMinimap();
timer = 0;
}
}

void UpdateMinimap()
{
// 更新小地图逻辑
}
}

3.6 避免”敏感操作”

操作 问题 优化方案
频繁 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 元素优化

4.1 血条优化

策略 说明
使用 Simple/Filled 模式 减少顶点数
相同图集 确保 DrawCall 合并
分帧加载 避免单帧创建过多
World Space Canvas 避免主角移动触发重建
1
2
3
4
5
6
7
// ✅ 使用 World Space Canvas 避免重建
// 将主角血条放在单独的 World Space Canvas
// 避免主角移动触发其他 UI 重建

Canvas hpCanvas = Instantiate(hpCanvasPrefab);
hpCanvas.renderMode = RenderMode.WorldSpace;
hpCanvas.worldCamera = uiCamera;

4.2 伤害飘字优化

问题 解决方案
频繁创建销毁 使用对象池
每帧更新位置 隔帧更新
数量爆炸 控制同屏总量
文字转图片 使用图片字体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ✅ 飘字对象池
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);
}

private DamageText CreateNew()
{
return Instantiate(damageTextPrefab).GetComponent<DamageText>();
}
}

4.3 HUD 优化总结

优化项 NGUI UGUI
血条模式 Simple/Filled Filled
图集 相同图集 相同 Sprite Atlas
分帧加载 必需 必需
主角分离 World Space UIPanel World Space Canvas
飘字池 对象池 对象池
位置更新 隔帧更新 隔帧更新
字体 图片字 TextMeshPro

五、针对性优化

5.1 文字优化

问题 原因 解决方案
Outline 开销大 复制 4 份文字 Mesh 使用 Shadow 替代
Shadow 性能 添加顶点实现阴影 使用 TextMeshPro
1
2
3
4
5
6
7
// ✅ TextMeshPro 优势
// - 更少的顶点数
// - 更好的字体渲染
// - 支持丰富的文字效果
// - 更好的性能

// 替换 Text 组件为 TextMeshPro

5.2 Mask 组件优化

组件 DrawCall 开销 OverDraw 替代方案
Mask +4 DC RectMask2D
RectMask2D +0 DC 仅矩形区域
MeshMask +1 DC 自定义形状

5.3 Image 组件优化

设置 效果
取消 Fill Center 九宫格中心镂空
禁用 Raycast Target 不需要交互的元素
避免 Tiled 类型 减少纹理采样
避免 Pixel Perfect 减少计算开销

5.4 空 Image 处理

1
2
3
4
5
6
7
8
9
10
11
// ❌ 空 Image 仍会渲染
<Image raycastTarget="true" />

// ✅ 自定义空 Graphic
public class EmptyGraphic : Graphic
{
protected override void OnPopulateMesh(VertexHelper vh)
{
// 不生成任何顶点
}
}

5.5 颜色动画优化

1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ 修改 Image.color 会触发网格重建
private Image image;
void Update()
{
image.color = targetColor; // 每帧重建网格
}

// ✅ 使用独立 Material 避免重建
private Material material;
void LateUpdate()
{
material.color = targetColor; // 只更新材质属性
}

六、优化技巧汇总

6.1 禁用组件清单

组件/功能 原因 替代方案
UI/Effect/Shadow 4 份 Mesh 顶点 TextMeshPro
UI/Effect/Outline 继承 Shadow 更开销 TextMeshPro SDF
Image.Tiled 高采样开销 自定义 Shader
Pixel Perfect 额外计算 手动布局
Text.BestFit 预生成所有字号 固定字号

6.2 检测重叠关系

1
2
3
4
5
6
7
8
9
线框模式检测:
┌─────────────────────────────────────┐
│ Frame Debug → Toggle Wireframe │
│ │
│ 用途: │
│ • 检查 UI 重叠关系 │
│ • 分析穿插问题 │
│ • 验证层级顺序 │
└─────────────────────────────────────┘

6.3 文字分层策略

原则 说明
文字顶层 所有文字放在渲染最顶层
图片底层 图片放在文字下方
拓扑排序 保持一致的层级顺序

6.4 多边形镂空

1
2
3
4
5
6
7
8
9
10
11
12
// 多边形镂空脚本
// 只显示可见区域,减少 OverDraw
// 参考: https://blog.uwa4d.com/archives/fillrate.html

public class PolygonImage : Image
{
protected override void OnPopulateMesh(VertexHelper vh)
{
base.OnPopulateMesh(vh);
// 自定义多边形镂空逻辑
}
}

6.5 事件拦截优化

1
2
3
4
5
6
// ❌ 所有 Graphic 都参与 Raycast
// 性能开销大

// ✅ 禁用不需要的 Raycast Target
Image image = GetComponent<Image>();
image.raycastTarget = false; // 纯展示元素

七、优化检查清单

7.1 快速检查

检查项 NGUI UGUI
DrawCall 数量 <50 <30
网格重建频率 检查 FillAll 调用 检查 BuildBatch
OverDraw <2x 平均 <2x 平均
动静分离
对象池

7.2 性能建议

场景 DrawCall 目标
PC 项目 <100
手游高端 <50
手游低端 <30

7.3 综合清单

  • DrawCall 数量达标
  • 网格重建频率合理
  • OverDraw 在目标范围内
  • 动静分离完成
  • 对象池已实现
  • 禁用 Raycast Target (不需要交互的)
  • 避免 Outline/Shadow
  • 避免 BestFit
  • 避免 Pixel Perfect
  • 避免 Tiled Image
  • 使用 RectMask2D 替代 Mask
  • HUD 使用 World Space
  • 飘字使用对象池
  • 文字分层正确
  • 图集规划合理

八、参考资料


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com