NGUI 完全指南 - 核心类解析与性能优化实战
深入解析 NGUI 框架的核心类、事件系统、渲染机制,并提供全面的性能优化策略。
一、NGUI 简介
1.1 什么是 NGUI
NGUI (Next-Gen UI) 是 Unity 中一个强大的 UI 框架,具有以下特点:
| 特点 |
说明 |
| 轻量高效 |
纯 C# 实现,无原生依赖 |
| 动态合批 |
自动合并 DrawCall |
| 完整事件 |
支持点击、拖拽、滚动等交互 |
| 可视化编辑 强大的 Inspector 面板 |
|
1.2 学习资源
二、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
| 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 |
| 属性 |
说明 |
优化建议 |
| 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); }
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
| 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); } }
|
八、辅助工具
1 2 3 4 5 6 7 8 9
| NGUI > Open > Panel Tool 功能: 查看 Panel 下的数据 ┌─────────────────────────────────────┐ │ Panel Tool 输出: │ │ ├─ DrawCall 数量 │ │ ├─ 三角形数量 │ │ ├─ 顶点数量 │ │ └─ 渲染顺序 │ └─────────────────────────────────────┘
|
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
| #define SHOW_HIDDEN_OBJECTS
|
九、图集与字体
9.1 图集 (Atlas)
| 作用 |
说明 |
| 减少 DC |
相同图集可合批 |
| 减少 DrawCall |
单次绘制多元素 |
| 内存优化 |
纹理合并减少开销 |
9.2 字体优化
| 字体类型 |
特点 |
使用场景 |
| 静态字体 |
预生成图集 |
固定文字 |
| 动态字体 |
实时生成 Mesh |
动态文字 (大量使用时慎用) |
十、优化检查清单
10.1 通用检查
10.2 内存优化
10.3 遗留问题
| 问题 |
解决方案 |
| 多血条性能 |
分层策略、World Space Canvas |
| 复杂动画重建 |
动静分离、独立 Panel |
| 动态字体开销 |
大量文字使用静态字体 |
十一、参考资料
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com