NGUI 完全指南 - 核心类解析与性能优化实战

深入解析 NGUI 框架的核心类、事件系统、渲染机制,并提供全面的性能优化策略。


一、NGUI 简介

1.1 什么是 NGUI

NGUI (Next-Gen UI) 是 Unity 中一个强大的 UI 框架,具有以下特点:

特点 说明
轻量高效 纯 C# 实现,无原生依赖
动态合批 自动合并 DrawCall
完整事件 支持点击、拖拽、滚动等交互
可视化编辑 强大的 Inspector 面板

1.2 学习资源

资源 链接
Asset Store NGUI Next-Gen UI
官方网站 Tasharen.com
官方文档 NGUI Documentation
官方视频 YouTube Playlist

二、UI 核心三要素

2.1 三要素概览

1
2
3
4
5
6
7
8
9
10
11
12
NGUI 三大核心要素:
┌─────────────────────────────────────┐
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 图片 │ │ 字体 │ │ 事件 │ │
│ │UISprite │ │UILabel │ │UICamera │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 纹理/图集 字体文件 事件响应系统 │
│ │
└─────────────────────────────────────┘
要素 组件 作用
图片 UISprite UI 显示的主要视觉元素
字体 UILabel 文字显示组件
事件 UICamera 处理用户交互输入

三、事件系统 (UICamera)

3.1 事件触发原理

1
2
3
4
5
6
7
8
9
10
11
事件检测流程:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 用户输入 │──>│ 屏幕像素 │──>│ 射线检测 │──>│ 碰撞盒 │
│鼠标/触摸 │ │ 点转换 │ │Raycast │ │Collider │
└─────────┘ └─────────┘ └─────────┘ └─────────┘


┌─────────────────────┐
│ 事件分发 │
│ onClick/OnDrag │
└─────────────────────┘

3.2 射线检测模式

模式 射线方式 碰撞检测 回调
World_3D Camera.ScreenPointToRay Physics.Raycast Ignore (不回调)
UI_3D Camera.ScreenPointToRay Physics.RaycastNonAlloc Collide (触发次数)
World_2D Plane.Raycast Physics2D.OverlapPoint Collider2D
UI_2D Plane.Raycast Physics2D.OverlapPointNonAlloc Collider2D

3.3 事件类型

事件 触发条件 典型用途
OnHover 鼠标悬停 按钮高亮
OnPress 按下/释放 按钮点击
OnClick 点击释放 菜单选择
OnDoubleClick 双击 (0.4s内) 快速操作
OnDragStart 开始拖动 拖拽物品
OnDrag 拖动中 滑动条
OnDragEnd 拖动结束 物品放置
OnKey 键盘输入 快捷键

3.4 事件监听实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ✅ 使用 UIEventListener
public class MyButton : MonoBehaviour
{
void Start()
{
UIEventListener listener = GetComponent<UIEventListener>();
listener.onClick = OnButtonClick;
listener.onHover = OnButtonHover;
}

void OnButtonClick(GameObject go)
{
Debug.Log("Button clicked: " + go.name);
}

void OnButtonHover(GameObject go, bool isHover)
{
// 处理悬停状态
}
}

四、渲染系统

4.1 渲染器组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Mesh 渲染所需数据:
┌─────────────────────────────────────┐
│ MeshRenderer │
│ ┌─────────────────────────────┐ │
│ │ Mesh │ │
│ │ ├─ vertices (顶点坐标) │ │
│ │ ├─ triangles (顶点索引) │ │
│ │ ├─ uv (纹理坐标) │ │
│ │ ├─ colors (顶点颜色) │ │
│ │ └─ normals (法线) │ │
│ └─────────────────────────────┘ │
│ │
│ ├─ Material (材质) │
│ ├─ Shader (着色器) │
│ └─ Color (颜色) │
└─────────────────────────────────────┘

4.2 渲染更新触发条件

数据变化 是否重新渲染 说明
vertices ✅ 是 顶点坐标改变
triangles ✅ 是 三角形索引改变
colors ✅ 是 颜色改变
uv ✅ 是 纹理坐标改变
normals ✅ 是 法线改变

⚠️ 重要:以上任何值改变都会触发 VBO 重新缓存!

4.3 VBO (Vertex Buffer Object)

1
2
3
4
5
6
7
8
9
10
11
12
13
VBO 结构:
┌─────────────────────────────────────┐
│ GPU 缓存区 │
│ ┌─────────────────────────────┐ │
│ │ 顶点信息 │ │
│ │ 颜色信息 │ │
│ │ 法线信息 │ │
│ │ 纹理坐标信息 │ │
│ │ 索引信息 │ │
│ └─────────────────────────────┘ │
│ │
│ 优化方向: 减少数据传输频率 │
└─────────────────────────────────────┘

五、核心类详解

5.1 UIRect

功能 说明
矩形框定义 规定 UI 矩形范围和锚点
相对锚点 设置相对于父 UI 的位置
位置更新 维护父子关系,检测变化
灵活定位 支持屏幕四角任一为原点

5.2 UIGeometry

1
2
3
4
5
6
7
8
9
10
UIGeometry 数据结构:
┌─────────────────────────────────────┐
│ UIGeometry │
│ ├─ verts (顶点坐标) │
│ ├─ uvs (纹理坐标) │
│ ├─ colors (顶点颜色) │
│ └─ normals (法线) │
│ │
│ 用于 UIWidget 的缓存数据 │
└─────────────────────────────────────┘
计算示例 三角形 顶点
1 个字母 ‘A’ 2 4
1 个汉字 ‘爱’ 2 4
‘爱你,爱世人’ 12 24

5.3 UIWidget

属性 说明 优化建议
Depth 渲染顺序 避免动态修改
矩形适配 UIRect 管理 使用 Anchor
数据填充 OnFill 方法 缓存不变数据
UIDrawCall DrawCall 引用 由 UIPanel 管理
1
2
3
4
5
6
7
8
9
10
11
// ❌ 不建议频繁修改
void Update()
{
widget.depth = Random.Range(0, 100); // 每次重建所有 DC
}

// ✅ 正确做法
void Start()
{
widget.depth = initialDepth; // 只设置一次
}

5.4 UIDrawCall

功能 说明
记录 UI 簇 管理一组可合批的 Widget
创建 Mesh 动态生成网格数据
设置渲染器 配置 MeshRenderer
更新机制 局部更新 vs 全量重建

💡 核心设计:一个 UIDrawCall = 一次 DrawCall = 一个 Batch

5.5 UIPanel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UIPanel 架构:
┌─────────────────────────────────────┐
│ UIPanel │
│ ├─ 管理 UIWidget 列表 │
│ ├─ 管理 UIDrawCall 列表 │
│ ├─ 渲染队列 (Render Q: 3000+) │
│ ├─ 深度排序 (Depth 排序) │
│ └─ 动态合批管理 │
│ │
│ LateUpdate: │
│ 1. 更新所有 Widget │
│ 2. 填充几何数据 │
│ 3. 创建 UIDrawCall │
│ 4. 更新 Mesh │
└─────────────────────────────────────┘

六、DrawCall 合批规则

6.1 合批条件

条件 要求
相同 UIPanel 必须属于同一个 Panel
相同图集 纹理必须一致
相同材质 Material 必须一致
连续 Depth 不能有其他材质穿插

6.2 Depth 排序示例

1
2
3
4
5
6
7
8
9
错误排序 (穿插):
图集A Depth:0, 2, 4, 6
字体 Depth: 1, 3, 5, 7
结果: 8 个 DrawCall

正确排序 (分组):
图集A Depth:0, 1, 2, 3
字体 Depth:4, 5, 6, 7
结果: 2 个 DrawCall

七、优化策略

7.1 性能标准

指标 目标值
CPU 耗时 0-5ms
UIPanel.LateUpdate 0.4-8.4ms
DrawCall 数量 <40
堆内存分配 <20MB/10K帧

7.2 优化技巧

技巧 说明 效果
动静分离 静态/动态 UI 分离 Panel 减少更新范围
Static 标志 静态界面勾选 Static 跳过深度计算
Depth 分层 图集与字体分开深度 减少 DC
元素隐藏 使用 Alpha=0 而非 SetActive 避免重建
移出屏幕 Position 移出视野 避免渲染

7.3 UIGeometry 缓存池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ✅ BetterList 实现
public class GeometryBufferPool
{
private Stack<List<Vector3>> vertexPool = new Stack<List<Vector3>>();
private Stack<List<int>> indexPool = new Stack<List<int>>();

public List<Vector3> GetVertexList(int capacity)
{
if (vertexPool.Count > 0)
{
var list = vertexPool.Pop();
list.Capacity = capacity;
return list;
}
return new List<Vector3>(capacity);
}

public void ReturnVertexList(List<Vector3> list)
{
list.Clear();
vertexPool.Push(list);
}
}

八、辅助工具

8.1 Panel Tool

1
2
3
4
5
6
7
8
9
NGUI > Open > Panel Tool
功能: 查看 Panel 下的数据
┌─────────────────────────────────────┐
│ Panel Tool 输出: │
│ ├─ DrawCall 数量 │
│ ├─ 三角形数量 │
│ ├─ 顶点数量 │
│ └─ 渲染顺序 │
└─────────────────────────────────────┘

8.2 Draw Call Tool

1
2
3
4
5
6
7
8
9
NGUI > Open > Draw Call Tool
功能: 显示所有 DC 信息
┌─────────────────────────────────────┐
│ Draw Call Tool 输出: │
│ ├─ DC 列表 │
│ ├─ Material 引用 │
│ ├─ Render Q 值 │
│ └─ 统计信息 │
└─────────────────────────────────────┘

8.3 宏定义

1
2
3
4
5
// UIDrawCall.cs
#define SHOW_HIDDEN_OBJECTS

// 启用后可在 Hierarchy 中看到所有 DC
// 每个 DC 的 Inspector 可查看详细信息

九、图集与字体

9.1 图集 (Atlas)

作用 说明
减少 DC 相同图集可合批
减少 DrawCall 单次绘制多元素
内存优化 纹理合并减少开销

9.2 字体优化

字体类型 特点 使用场景
静态字体 预生成图集 固定文字
动态字体 实时生成 Mesh 动态文字 (大量使用时慎用)

十、优化检查清单

10.1 通用检查

  • DrawCall 数量 < 40
  • UIPanel.LateUpdate 耗时 < 5ms
  • 图集与字体分层设置
  • 静态 UI 勾选 Static
  • 避免频繁修改 Depth
  • 使用 Alpha 隐藏而非 SetActive

10.2 内存优化

  • 使用 BetterList 替代 List
  • 实现 UIGeometry 缓存池
  • 设置合理的 Capacity
  • 使用 4 倍扩容而非 2 倍
  • 避免频繁创建销毁数组

10.3 遗留问题

问题 解决方案
多血条性能 分层策略、World Space Canvas
复杂动画重建 动静分离、独立 Panel
动态字体开销 大量文字使用静态字体

十一、参考资料


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