19.1 性能优化的核心指标
| 指标 |
目标 |
说明 |
| FPS |
≥ 30(移动端),≥ 60(PC) |
帧率 |
| Draw Calls |
< 200(移动端),< 1000(PC) |
CPU 向 GPU 发送的渲染命令数 |
| Tris/Verts |
视平台而定 |
每帧渲染的三角形/顶点数 |
| SetPass Calls |
越低越好 |
切换渲染状态的次数 |
| Memory |
不超设备限制 |
内存占用(纹理、网格、音频) |
19.2 使用 Profiler
Window → Analysis → Profiler(Ctrl+7)
主要模块
| 模块 |
用途 |
| CPU Usage |
各项操作的 CPU 耗时 |
| Rendering |
渲染相关数据(Draw Calls、Tris 等) |
| Memory |
内存分配和回收 |
| GPU Usage |
GPU 执行时间 |
| Audio |
音频性能 |
Profiler 使用技巧
- 连接真机进行性能分析(Profile 真实设备表现)
- 关注 GC Alloc 列,寻找不必要的内存分配
- Deep Profile 模式(性能开销大但能看到每个函数的调用耗时)
- 使用 Profiler Markers 标记自定义代码段
19.3 渲染优化
Draw Call 合并
1 2 3 4 5 6 7 8 9 10 11
| Debug.Log($"当前 Draw Calls: {UnityStats.drawCalls}");
|
优化原则
- 材质数量越少越好:多个物体共用同一材质减少 SetPass Calls
- 纹理图集(Atlas):将多个小纹理合并为一张大纹理
- LOD(Level of Detail):远距离使用低面模型
- 遮挡剔除(Occlusion Culling):被遮挡的物体不渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class LODController : MonoBehaviour { [SerializeField] private GameObject highDetail; [SerializeField] private GameObject lowDetail; [SerializeField] private float distanceThreshold = 20f;
void Update() { float dist = Vector3.Distance( transform.position, Camera.main.transform.position ); highDetail.SetActive(dist < distanceThreshold); lowDetail.SetActive(dist >= distanceThreshold); } }
|
19.4 脚本优化
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| void Update() { GameObject player = GameObject.Find("Player"); }
private Transform player;
void Start() { player = GameObject.Find("Player").transform; }
void Update() { }
void Update() { Vector3[] positions = new Vector3[10]; }
private Vector3[] positions = new Vector3[10];
void Update() { }
void Update() { Debug.Log("Health: " + health + "/" + maxHealth); }
private StringBuilder sb = new StringBuilder(64);
void Update() { sb.Clear(); sb.Append("Health: ").Append(health).Append("/").Append(maxHealth); Debug.Log(sb.ToString()); }
|
19.5 内存优化
资源管理
1 2 3 4 5 6 7
| AssetBundle bundle = AssetBundle.LoadFromFile(path); GameObject obj = bundle.LoadAsset<GameObject>("Enemy"); bundle.Unload(false);
|
纹理压缩
| 平台 |
推荐格式 |
| iOS |
ASTC |
| Android |
ETC2(支持 OpenGL ES 3.0) |
| Desktop |
DXT / BC7 |
其他技巧
- 使用 Sprite Atlas 合并 2D 图集
- 禁用不必要的组件(如禁用未使用的 AudioSource)
- 对象池(详见第 16 章)减少 GC 压力
- 使用 Object.DontDestroyOnLoad 谨慎(对象不会被卸载)
19.6 各平台优化目标参考
| 平台 |
目标 FPS |
建议 Draw Calls |
建议 Tris |
| 低端 Android |
30 |
< 100 |
< 100k |
| 中端 Android |
30-60 |
< 200 |
< 200k |
| iOS(iPhone 12+) |
60 |
< 300 |
< 300k |
| PC |
60+ |
< 1000 |
< 500k |
| Console |
30-60 |
< 800 |
< 500k |
19.7 本章小结
- 性能优化的核心是减少 Draw Calls 和内存分配
- Profiler 是定位性能瓶颈的首选工具
- 静态合批、GPU Instancing、LOD、遮挡剔除是渲染优化四大手段
- 脚本优化重在缓存引用和避免每帧分配
- 纹理压缩和资源按需加载减少内存占用
- 优化应该先定位瓶颈再精准优化,避免过度优化
练习题
- 使用 Profiler 分析场景,找出 CPU 耗时最高的模块
- 在场景中放置 1000 个 Cube,对比开启/关闭静态合批的 Draw Calls
- 用对象池优化一段频繁 Instantiate/Destroy 的代码,对比 GC Alloc
- 为角色模型设置 LOD Group,观察不同距离的面数和性能变化