Unity 代码优化实战 - 从规范到 Profiler 全栈指南

涵盖代码规范、GC 优化、闭包处理、组件优化、UI 规范、Shader/CPU 优化及 Profiler 分析的完整优化指南。


一、编程代码规范

1.1 基本规范

规范 说明
脚本行数 单个脚本不超过 500 行
多语言版本 不同语言版本独立 Copy 一份代码
声音配置 由策划写在 Excel 中
链式语法 使用 C# 扩展方法提升可读性
平台测试 代码必须在多平台测试通过

1.2 扩展方法示例

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 WWWFormEx
{
private WWWForm form = new WWWForm();

public WWWFormEx AddField(string name, string value)
{
form.AddField(name, value);
return this; // 返回自身支持链式调用
}

public WWWFormEx AddBinary(string name, byte[] data)
{
form.AddBinaryData(name, data);
return this;
}
}

// 使用
WWWForm form = new WWWFormEx()
.AddField("username", "player")
.AddField("password", "123456")
.AddBinary("avatar", avatarData);

二、代码控制与 GC 优化

2.1 避免产生 GC

写法 GC 产生 说明
foreach ✅ 产生 每次 foreach 产生枚举器
for 循环 ❌ 无 使用索引直接访问
字典遍历 ✅ 产生 需使用枚举器优化
字符串拼接 ✅ 产生 超过 10 次需用 StringBuilder

2.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
// ❌ foreach 产生 GC
foreach (var item in list)
{
item.Update();
}

// ✅ 使用 for 替代
for (int i = 0; i < list.Count; i++)
{
list[i].Update();
}

// ❌ 字典遍历产生 GC
foreach (var kvp in dictionary)
{
kvp.Value.Update();
}

// ✅ 使用枚举器
var enumerator = dictionary.GetEnumerator();
while (enumerator.MoveNext())
{
var element = enumerator.Current;
element.Value.Update();
}
enumerator.Dispose();

2.3 字符串拼接优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ✅ 少量拼接不产生 GC
string result = "Hello" + " " + "World"; // < 10 次

// ❌ 大量拼接产生 GC
for (int i = 0; i < 100; i++)
{
result += i.ToString();
}

// ✅ 使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++)
{
sb.Append(i);
}
string result = sb.ToString();

2.4 Struct vs Class

特性 Struct Class
内存分配
GC 影响
适用场景 轻量数据 复杂对象、抽象层次
1
2
3
4
5
6
7
8
9
10
11
// ✅ 轻量数据使用 Struct
public struct Point
{
public float x;
public float y;
}

// ⚠️ Struct 修改后需重新赋值
Point p = new Point();
p.x = 10; // 修改的是副本
// 需要重新赋值给原变量

2.5 集合选择

集合类型 特点 适用场景
数组 连续存储,索引快 大小固定
ArrayList 动态伸缩,非类型安全 已过时
List<T> 类型安全,动态扩展 通用集合

2.6 其他注意事项

  • ❌ 不要把枚举当字典的 TKey 使用
  • ❌ 不要把枚举转成 string 使用

三、闭包与委托

3.1 变量作用域

类型 作用域
成员变量 作用于整个类
局部变量 作用于函数内
次局部变量 作用于函数局部片段

3.2 委托与闭包

1
2
3
4
5
6
7
8
9
10
11
12
闭包本质:
┌─────────────────────────────────────┐
│ 1. 代码块维护创建时的执行上下文 │
│ → 即使父方法执行完仍可访问变量 │
│ │
│ 2. 闭包关闭的是变量,不是值 │
│ → Closures close over variables │
│ → not over values │
│ │
│ 3. 闭包引用外部变量会生成新类 │
│ → 频繁调用时避免使用闭包 │
└─────────────────────────────────────┘

3.3 闭包优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ❌ 闭包生成额外类
for (int i = 0; i < 10; i++)
{
StartCoroutine(() => DoSomething(i)); // 每次创建新类
}

// ✅ 提取为独立方法
for (int i = 0; i < 10; i++)
{
StartCoroutine(DoSomethingCoroutine(i));
}

IEnumerator DoSomethingCoroutine(int value)
{
// 使用 value
}

四、MonoBehaviour 优化

4.1 基本优化

技巧 说明
删除空函数 没有事件处理的空函数应删除
缓存组件 Start 中缓存 Find 结果
降低频率 隔帧执行
使用协程 替代 Update
禁用不可见 OnBecameInvisible 设置 enabled = false

4.2 Update 优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ✅ 降低更新频率
void Update()
{
if (Time.frameCount % 6 == 0)
{
DoSomething();
}
}

// ✅ 使用 InvokeRepeating
void Start()
{
InvokeRepeating("DoSomething", 0.5f, 1.0f);
}

// ✅ 使用协程
IEnumerator Start()
{
while (true)
{
DoSomething();
yield return new WaitForSeconds(1.0f);
}
}

4.3 协程优化

1
2
3
4
5
6
7
8
9
10
// ❌ yield return null 每帧产生 9 字节 GC
yield return null;

// ✅ 预生成 Wait 对象
private WaitForSeconds waitOneSecond = new WaitForSeconds(1f);

IEnumerator TestCoroutine()
{
yield return waitOneSecond;
}

五、Component 优化

5.1 使用内建值

1
2
3
4
5
6
7
// ❌ 每次创建新对象
transform.position = new Vector3(0, 0, 0);

// ✅ 使用内建常量
transform.position = Vector3.zero;
transform.localScale = Vector3.one;
transform.localRotation = Quaternion.identity;

六、GameObject 优化

6.1 缓存组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ 每次查找
void Update()
{
var render = GetComponent<Renderer>();
}

// ✅ 缓存组件
private Renderer cachedRenderer;

void Start()
{
cachedRenderer = GetComponent<Renderer>();
}

void Update()
{
if (cachedRenderer != null)
{
// 使用缓存的组件
}
}

6.2 标签比较

1
2
3
4
5
6
7
8
9
// ❌ 产生 180B GC
if (go.tag == "Enemy")
{
}

// ✅ 不产生 GC
if (go.CompareTag("Enemy"))
{
}

6.3 避免 SendMessage

1
2
3
4
5
6
// ❌ 性能开销大
gameObject.SendMessage("DoSomething");

// ✅ 使用委托或直接调用
IDoSomething handler = go.GetComponent<IDoSomething>();
handler?.DoSomething();

七、NGUI vs UGPU 性能对比

特性 NGUI UGUI
合批位置 主线程 UIPanel.LateUpdate() 子线程 Canvas.BuildBatch()
网格生成 上层创建 Vertex List 底层 C++ 代码实现
堆内存管理 频繁分配 更好的 GC 表现

八、UI 资源规范(内存优化)

8.1 图集规范

规范 说明
最大尺寸 1024×1024
同界面同图集 减少 DrawCall
公用资源 放 Common 重复利用
九宫格 减小原图大小
关闭 Mipmaps UI 不需要

8.2 原图处理

处理方式 说明
全屏原图 按比例缩放最长边到 500
长条原图 拆分拼接(如 1000×100 → 两个 500×100)
图集利用率 >1/3,否则合并
复用资源 使用顶点色区分品质

8.3 颜色复用示例

1
2
3
4
5
6
7
8
9
10
// ❌ 使用五张大底图
Image qualityBG_Blue;
Image qualityBG_Green;
Image qualityBG_Yellow;
// ...

// ✅ 使用一张底图 + 顶点色
Image qualityBG;
qualityBG.color = Color.blue; // 蓝色品质
qualityBG.color = Color.green; // 绿色品质

九、GPU 优化

9.1 Shader 优化

优化项 说明
Fog { Mode Off } 关闭雾效
Alpha 剔除 剔除 Alpha=0 的像素,减少 OverDraw
Fill Center 九宫格中心镂空

9.2 OverDraw 优化

策略 说明
减少层级 减少 UI 重叠
隐藏元素 Disable 或移出裁剪区域
避免空 Image 不使用 Alpha=0 的响应区域

十、CPU 优化

10.1 DrawCall 优化

策略 说明
合批绘制 相同图集合并
相邻同图集 避免穿插
Text 分层 避免打断合批
线框模式检测 Scene 视图 Wireframe

10.2 Mask 组件

1
2
3
4
5
// ❌ Mask 使用 Stencil Buffer,打断合批
// Unity 5.2+ 建议使用 RectMask2D

// ✅ 使用 RectMask2D
var rectMask = gameObject.AddComponent<RectMask2D>();

十一、Profiler 详解

11.1 重要函数

函数 说明 优化方向
WaitForTargetFPS Vsync 等待时间 检查 Vsync 设置
Camera.Render 相机渲染准备 优化渲染内容
Shader.Parse Shader 解析 预加载 Shader
Reserved Total 系统申请内存 控制内存分配
GC.Collect 垃圾回收 减少内存分配

11.2 优化重点

类型 重点 检测项
GC Alloc 一次性分配 >2KB Profiler Memory
GC Alloc 每帧分配 >20B Profiler Memory
Time ms 占用 >5ms Profiler CPU
ManagedHeap 移动游戏 <20MB Profiler Memory
Device.Present GPU 等待时间长 检查 Shader 复杂度

11.3 常见性能热点

函数 原因 解决方案
Canvas.SendWillRenderCanvases UI 重建 减少 RectTransform 变化
Canvas.BuildBatch 合批开销 合理规划图集
Debug.Log 产生堆栈 发布版本屏蔽
StackTraceUtility 堆栈追踪 减少 Debug 调用

十二、优化检查清单

12.1 代码优化

  • 避免使用 foreach
  • 使用字典枚举器
  • 字符串使用 StringBuilder
  • Struct 替代 Class(轻量对象)
  • 避免闭包和匿名函数
  • 缓存组件引用
  • 使用 CompareTag 替代 tag 比较
  • 避免 SendMessage
  • 降低 Update 频率
  • 预生成 Wait 对象
  • 禁用不可见 GameObject

12.2 UI 优化

  • 图集 ≤1024×1024
  • 关闭 Mipmaps
  • 同界面同图集
  • 使用九宫格
  • 移除 Fill Center
  • 减少 Mask 使用
  • 动静分离
  • 禁用 Raycast Target

12.3 资源优化

  • 纹理使用压缩格式
  • 及时释放资源
  • 合并图集
  • 移除重复资源
  • 压缩音频文件
  • 使用 Force to Mono

十三、参考资料


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