Unity 代码编写规范与优化标准 - 从代码质量到性能调优
涵盖代码优化标准、内存管理、渲染优化、AssetBundle 策略、项目发布及 FPS 标准的完整开发规范。
一、优化标准概述
1.1 行业标准理念
1 2 3 4 5 6 7 8 9 10 11 12
| 优化标准体系: ┌─────────────────────────────────────┐ │ │ │ 不同游戏有不同标准 │ │ ↓ │ │ 但存在行业内基准 │ │ ↓ │ │ 可在合理范围内上下浮动 │ │ ↓ │ │ 判断游戏水平: 差 / 中 / 好 │ │ │ └─────────────────────────────────────┘
|
1.2 标准分类
| 标准类型 |
关注点 |
重要性 |
| 代码优化 |
GC Alloc、函数调用开销 |
⭐⭐⭐⭐⭐ |
| 内存优化 |
堆内存、资源内存、内存泄露 |
⭐⭐⭐⭐⭐ |
| 渲染优化 |
DrawCall、Triangle、VBO |
⭐⭐⭐⭐ |
| FPS 标准 |
帧率稳定性、设备适配 |
⭐⭐⭐⭐⭐ |
二、代码优化标准
2.1 Lambda 表达式
| 规范 |
说明 |
风险 |
| 尽量避免 |
减少使用 Lambda 表达式 |
内存泄露隐患 |
1 2 3 4 5 6 7 8 9 10 11 12
| button.onClick.AddListener(() => { Debug.Log("Clicked"); });
button.onClick.AddListener(OnButtonClick);
void OnButtonClick() { Debug.Log("Clicked"); }
|
2.2 LINQ 查询
| 规范 |
说明 |
影响 |
| 尽量避免 |
减少使用 LINQ |
大量 GC Alloc |
1 2 3 4 5 6 7 8 9 10
| var activeEnemies = enemies.Where(e => e.isActive).ToList();
List<Enemy> activeEnemies = new List<Enemy>(); foreach (var enemy in enemies) { if (enemy.isActive) activeEnemies.Add(enemy); }
|
2.3 协程控制
| 规范 |
说明 |
内存开销 |
| 控制使用次数 |
减少 StartCoroutine 调用 |
每次至少 37B |
| Coroutine 类 |
21B |
- |
| Enumerator |
16B |
- |
优化方案:
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
| void Start() { StartCoroutine(DoSomething()); }
private IEnumerator globalCoroutine;
void Start() { globalCoroutine = GlobalCoroutineManager(); StartCoroutine(globalCoroutine); }
IEnumerator GlobalCoroutineManager() { while (true) { yield return new WaitForSeconds(1f); } }
private float timer; void Update() { timer += Time.deltaTime; if (timer >= 1f) { DoSomething(); timer = 0; } }
|
2.4 字符串拼接
| 方式 |
GC 影响 |
推荐场景 |
| String + String |
频繁申请释放内存 |
❌ 不推荐 |
| StringBuilder |
同一内存块操作 |
✅ 推荐 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| string result = ""; for (int i = 0; i < 100; i++) { result += i.ToString(); }
StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100; i++) { sb.Append(i); } string result = sb.ToString();
|
2.5 组件缓存
| 规范 |
说明 |
GC 影响 |
| 提前缓存 |
Start 中缓存组件 |
避免 GetComponent |
| 避免获取名称 |
不使用 Object.name |
分配 39B |
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() { var renderer = GetComponent<Renderer>(); var name = gameObject.name; }
private Renderer cachedRenderer; private string cachedName;
void Start() { cachedRenderer = GetComponent<Renderer>(); cachedName = gameObject.name; }
void Update() { if (cachedRenderer != null) { } }
|
2.6 避免开销大的函数
| 函数 |
开销级别 |
替代方案 |
| GameObject.Find |
⭐⭐⭐⭐⭐ |
缓存引用 |
| GameObject.FindWithTag |
⭐⭐⭐⭐ |
缓存引用 |
| Object.FindObjectOfType |
⭐⭐⭐⭐⭐ |
单例模式 |
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() { var player = GameObject.Find("Player"); }
private GameObject player;
void Start() { player = GameObject.Find("Player"); }
public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; }
void Awake() { Instance = this; } }
|
2.7 标签比较
| 方式 |
GC 分配 |
推荐 |
| gameObject.tag |
180B |
❌ |
| CompareTag() |
0B |
✅ |
1 2 3 4 5 6 7 8 9
| if (gameObject.tag == "Enemy") { }
if (gameObject.CompareTag("Enemy")) { }
|
2.8 对象池
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
| public class BulletPool : MonoBehaviour { [SerializeField] private Bullet bulletPrefab; private Queue<Bullet> pool = new Queue<Bullet>(); public int maxSize = 50;
public Bullet Get() { if (pool.Count > 0) { var bullet = pool.Dequeue(); bullet.gameObject.SetActive(true); return bullet; } return Instantiate(bulletPrefab); }
public void Return(Bullet bullet) { if (pool.Count < maxSize) { bullet.gameObject.SetActive(false); pool.Enqueue(bullet); } else { Destroy(bullet.gameObject); } } }
|
2.9 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() { ComplexCalculation(); }
void Update() { if (Time.frameCount % 6 == 0) { ComplexCalculation(); } }
IEnumerator Start() { while (true) { ComplexCalculation(); yield return new WaitForSeconds(0.5f); } }
|
2.10 函数调用优化
| 规范 |
说明 |
| 避免递归 |
减少函数调用栈 |
| 避免无限循环 |
仅在核心功能使用 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int Factorial(int n) { if (n <= 1) return 1; return n * Factorial(n - 1); }
int Factorial(int n) { int result = 1; for (int i = 2; i <= n; i++) result *= i; return result; }
x = Mathf.Abs(x);
x = (x > 0 ? x : -x);
|
三、纹理优化
3.1 压缩格式
| 平台 |
推荐格式 |
说明 |
| iOS |
PVRTC |
Apple 专属格式 |
| Android |
ETC / ETC2 |
通用格式 |
| Windows |
DXT / BC |
DirectX 格式 |
3.2 MipMaps 设置
| 场景 |
MipMaps |
说明 |
| 3D 场景 |
✅ 启用 |
提升远距离渲染质量 |
| UI / 2D |
❌ 禁用 |
避免内存浪费 |
1 2 3 4 5 6 7 8
| MipMaps 优化策略: ┌─────────────────────────────────────┐ │ 硬件 MipMaps 不满足需求: │ │ │ │ 1. 使用自定义生成的 Mipmaps │ │ 2. 启用 Anisotropic Filtering │ │ │ └─────────────────────────────────────┘
|
3.3 纹理大小优化
| 方法 |
效果 |
| 减色 |
降低纹理大小 |
| 调整尺寸 |
根据实际显示需求 |
四、内存优化
4.1 内存目标值
| 指标 |
目标值 |
说明 |
| 总内存 |
<150MB |
移动端标准 |
| 堆内存 |
<40MB |
Mono 堆内存 |
4.2 场景切换优化
1 2 3 4 5 6 7 8 9 10 11 12
| 场景切换内存管理: ┌─────────────────────────────────────┐ │ 问题: 两个场景内存叠加 → 峰值 │ │ │ │ 解决方案: │ │ 1. 显示 Loading 遮罩 │ │ 2. 等待旧场景释放完成 │ │ 3. 加载新场景 │ │ 4. 新场景初始化完成 │ │ 5. 隐藏 Loading 遮罩 │ │ │ └─────────────────────────────────────┘
|
4.3 内存控制策略
| 策略 |
说明 |
| 使用 Prefab |
场景物体应制作成 Prefab |
| 对象池 |
避免频繁 Instance/Destroy |
| 公共 UI 不释放 |
切换场景时保留 |
| 顺序加载 |
LoadManager 控制同时加载一个 WWW |
4.4 资源内存限制
| 资源类型 |
峰值限制 |
| 纹理 |
<50MB |
| 网格 |
<20MB |
| 动画片段 |
<15MB |
| 音频 |
<15MB |
4.5 内存泄露检测
1 2 3 4 5 6 7 8 9 10 11
| 内存健康标准: ┌─────────────────────────────────────┐ │ ✅ 内存升降一致 │ │ ❌ 持续上升(内存泄露) │ │ │ │ 检测方法: │ │ - 反复进入/退出场景 │ │ - 观察 Profiler 内存曲线 │ │ - 确保内存回归基准线 │ │ │ └─────────────────────────────────────┘
|
五、渲染优化
5.1 渲染目标值
| 指标 |
目标值 |
说明 |
| DrawCall 峰值 |
<250 |
主体范围 0-200 |
| Triangle 峰值 |
<100,000 |
每帧面数 |
| VBO 上传量 |
<5MB |
使用缓存池 |
| Skinned Mesh |
<50 |
蒙皮网格数量 |
| RenderTexture |
<10 |
渲染纹理数量 |
5.2 DrawCall 优化
六、CPU 优化
6.1 物理优化
| 指标 |
峰值 |
Profiler 对应 |
| Rigidbody |
<50/帧 |
Physics.Simulate |
| 碰撞体 |
<100/帧 |
Physics.Simulate |
七、AssetBundle 策略
7.1 依赖性打包
1 2 3 4
| string[] dependencies = AssetDatabase.GetDependencies("Assets/Prefabs/Player.prefab", false);
|
7.2 WWW vs CreateFromFile
| 方式 |
适用场景 |
压缩格式 |
| WWW |
远程加载 |
Unity 标准压缩 |
| CreateFromFile |
本地加载 |
非压缩格式 |
1 2 3 4 5 6 7 8 9 10
| IEnumerator LoadFromRemote(string url) { WWW www = new WWW(url); yield return www; AssetBundle bundle = www.assetBundle; }
AssetBundle bundle = AssetBundle.CreateFromFile(path);
|
7.3 AssetBundle.mainAsset
| 用途 |
说明 |
| 标准资源 |
存储 AB 中最重要的资源 |
| TextAsset |
记录 AB 内物体列表和信息描述 |
1 2 3 4 5
| AssetBundle bundle = AssetBundle.LoadFromFile(path); TextAsset manifest = bundle.mainAsset as TextAsset;
|
7.4 Build AssetBundles
1 2 3 4 5 6 7 8 9 10 11
| 开发 vs 发布策略: ┌─────────────────────────────────────┐ │ 开发阶段: │ │ → Resources.LoadAssetAtPath │ │ → 避免反复 Build AB │ │ │ │ 发布阶段: │ │ → BuildPipeline.BuildAssetBundle │ │ → 唯一脚本打包 │ │ │ └─────────────────────────────────────┘
|
7.5 AssetDatabase.ImportAsset
| 事实 |
说明 |
| 1 |
转换外部 asset 为内部识别格式 |
| 2 |
根据 AssetImporter 修改属性 |
| 3 |
Build 前强制重新导入 |
1 2 3 4
| - TextureImporter - ModelImporter - MovieImporter
|
7.6 预处理和后处理
AssetPostprocessor 作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Asset 导入流程: ┌─────────────────────────────────────┐ │ │ │ Import 开始 │ │ ↓ │ │ OnPreprocessXXX ← 用户介入修改 │ │ ↓ │ │ Unity 导入处理 │ │ ↓ │ │ OnPostprocessXXX ← 用户介入修改 │ │ ↓ │ │ Import 完成 │ │ │ └─────────────────────────────────────┘
|
八、项目发布策略
8.1 移动芯片支持
| Unity 版本 |
ARM V6 |
ARM V7 |
| 3.5.x |
✅ 支持 |
✅ 支持 |
| 4.0+ |
❌ 不支持 |
✅ 支持 |
8.2 Graphics Emulation
| 用途 |
说明 |
| 模拟测试 |
Editor 中模拟不同图形硬件 |
| 设置路径 |
Editor → Graphics Emulation |
8.3 代码保护
| 平台 |
保护方式 |
安全性 |
| iOS |
原生编译 |
✅ 高 |
| Android |
AssetBundle + TextAsset / 代码混淆 |
⚠️ 中 |
| 通用 |
Native DLL |
✅ 高 |
8.4 增量更新方案
| 方案 |
优点 |
缺点 |
| 脚本与资源全分离 |
完全动态 |
项目管理复杂,无法编辑器动态编辑 |
| 接口与实现分离 |
工作流不变,可编辑 |
需要额外架构设计 |
1 2 3 4 5 6 7 8 9 10 11
| 增量更新原理: ┌─────────────────────────────────────┐ │ 编译后的脚本无法修改 │ │ ↓ │ │ 但可以添加新脚本 │ │ ↓ │ │ 接口不变,实现可替换 │ │ ↓ │ │ 实现 AB 热更新 │ │ │ └─────────────────────────────────────┘
|
九、FPS 帧率标准
9.1 目标值
| 设备类型 |
目标帧率 |
最低可接受 |
| 高端机 |
60 FPS |
>50 FPS |
| 中端机 |
45 FPS |
>30 FPS |
| 低端机 |
30 FPS |
≥24 FPS |
9.2 低端机标准
| 参考设备 |
CPU 型号 |
| 红米 Note 4 |
Helio X20 |
9.3 优化阈值
1 2 3 4 5 6 7 8
| FPS 优化决策: ┌─────────────────────────────────────┐ │ FPS ≥ 50: 优秀 │ │ FPS ≥ 30: 可接受 │ │ FPS < 24: ❌ 必须优化 │ │ FPS < 20: ❌❌ 严重性能问题 │ │ │ └─────────────────────────────────────┘
|
十、UWA 内存优化
10.1 四种测试模式
| 模式 |
名称 |
用途 |
| 模式 1 |
Overview |
整体性能分析 |
| 模式 2 |
Mono |
堆内存分析(IL2CPP 也需测试) |
| 模式 3 |
Assets |
资源检测,重复/耗费 |
| 模式 4 |
Lua |
Lua 内存分析(收费) |
10.2 测试建议
1 2 3 4 5 6 7 8 9
| UWA 测试流程: ┌─────────────────────────────────────┐ │ 1. Overview 模式 → 整体评估 │ │ 2. Mono 模式 → 堆内存分析 │ │ (即使 IL2CPP 也需测试) │ │ 3. Assets 模式 → 资源优化 │ │ 4. Lua 模式 → Lua 优化(可选) │ │ │ └─────────────────────────────────────┘
|
十一、UPR 内存优化
11.1 UPR 优势
| 特性 |
说明 |
| 官方支持 |
Unity 官方性能报告工具 |
| 免费使用 |
无需付费 |
| 深度分析 |
覆盖多维度性能指标 |
十二、优化检查清单
12.1 代码优化
12.2 内存优化
12.3 渲染优化
12.4 FPS 目标
十三、参考资料
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com