🎨 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 网格更新机制

1
2
3
4
5
6
// NGUI 两种更新模式
// 1. UIPanel.FillDrawCall - 更新单个 DrawCall
// 2. UIPanel.FillAllDrawCalls - 更新所有 DrawCall (CPU 峰值!)

// 检测网格更新
// 在 UIPanel 上查看 Widgets OnPanel / Panels Created

⚠️ 重要: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; // 0.5秒更新一次
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
// ✅ 使用 World Space Canvas 避免重建
// 将主角血条放在单独的 World Space Canvas
// 避免主角移动触发其他 UI 重建
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
1
2
3
4
5
// ✅ 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 仍会渲染
<Image raycastTarget="true" />

// ✅ 使用自定义 Graphic 解决
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