本文原地址:Unity学习—脚本优化Tips
官方文档参考
代码编译原理
Unity 首先将脚本编译为中间语言 CIL (Common Intermediate Language ),CIL可再被编译为不同的原生语言。然后对不同的平台 Unity 采用不同的编译方式,分为运行前 AOT(Ahead of Time) 和运行时 JIT(Just in Time) 编译
C# ==> CIL ==> AOT(build) / JIT(device)
源代码编译为托管代码(managed code),托管代码编译为原生代码,托管运行时(managed runtime)管理整合,负责内存自动管理、安全检查等
程序执行时,CPU在引擎代码和托管代码之间进行安全检查,数据格式转换称为编组(marshalling),存在消耗但不是特别大
影响代码运行原因
- 糟糕代码结构
方法多次无效调用 - 调用其他不必要大开销代码
引擎代码和原生代码的相互调用 - 不必要时机调用
过早进行物理判断、画面判断 - 代码本身占用过高
大量计算
优化方式
避免大量调用Unity API
利用profile监测游戏调试
SendMessage() and BroadcastMessage()消耗大,尽量直接对象调用或者使用Events或Delegates
Find() 使用引用持有、Inspector面板引用、使用专用管理类
Transform 修改对象transform属性会触发子对象OnTransformChanged,子对象越多消耗越大。可将需要频繁修改的transform属性存入Vector3对象进行计算,再将Vector赋值回transform
Transform.position每次调用会计算一次,Transform.localPosition调用则直接取值
Update() 空方法也会占用CPU调用资源,所以当Update为空时直接删除
Vector2 and Vector3 将向量计算替换为基本类型计算,例 Vector3.magnitude、 Vector3.sqrMagnitude
Camera.main 同Find()
条件判断前置
void Update()
{
for (int i = 0; i < myArray.Length; i++)
{
if (exampleBool)
{
ExampleFunction(myArray[i]);
}
}
}
void Update()
{
if (exampleBool)
{
for (int i = 0; i < myArray.Length; i++)
{
ExampleFunction(myArray[i]);
}
}
}
仅在展示变量变化时调用展示代码
private int score;
public void IncrementScore(int incrementBy)
{
score += incrementBy;
}
void Update()
{
DisplayScore(score);
}
private int score;
public void IncrementScore(int incrementBy)
{
score += incrementBy;
DisplayScore(score);
}
控制代码调用帧数
void Update()
{
ExampleExpensiveFunction();
}
private int interval = 3;
void Update()
{
if (Time.frameCount % interval == 0)
{
ExampleExpensiveFunction();
}
else if (Time.frameCount % interval == 1)
{
AnotherExampleExpensiveFunction();
}
}
使用缓存
void Update()
{
Renderer myRenderer = GetComponent();
ExampleFunction(myRenderer);
}
private Renderer myRenderer;
void Start()
{
myRenderer = GetComponent();
}
void Update()
{
ExampleFunction(myRenderer);
}
使用适当的数据结构
微软官方建议
减少垃圾收集
使用对象池,如敌人、子弹
剔除 Culling
void Update()
{
UpdateTransformPosition();
UpdateAnimations();
}
private Renderer myRenderer;
void Start()
{
myRenderer = GetComponent();
}
void Update()
{
UpdateTransformPosition();
if (myRenderer.isVisible)
{
UpateAnimations();
}
}
Level of detail
细节等级
垃圾处理优化
简介
Unity 核心代码为手动内存管理,开发代码自动内存管理同栈区堆区
栈区回收立即销毁、堆区回收会再在重新分配时清空
垃圾收集器标记和销毁堆区内存,收集器定期清理堆
垃圾收集器执行时机Garbage Collector
- 堆区无足够控件分配
- 自动周期执行
- 手动执行
垃圾收集器执行步骤
- 检查堆区对象
- 查找所有引用
- 标记空闲对象
- 还原堆空间
问题
- 堆区对象越多执行时间越长
- 错误执行时机,如 CPU高占用时调用
- 堆碎片化,可分配空间不足,导致收集器频繁调用
方案
提前缓存
void OnTriggerEnter(Collider other)
{
Renderer[] allRenderers = FindObjectsOfType();
ExampleFunction(allRenderers);
}
private Renderer[] allRenderers;
void Start()
{
allRenderers = FindObjectsOfType();
}
void OnTriggerEnter(Collider other)
{
ExampleFunction(allRenderers);
}
不在频繁调用的方法里分配
void Update()
{
ExampleGarbageGeneratingFunction(transform.position.x);
}
改为条件执行
private float previousTransformPositionX;
void Update()
{
float transformPositionX = transform.position.x;
if (transformPositionX != previousTransformPositionX)
{
ExampleGarbageGeneratingFunction(transformPositionX);
previousTransformPositionX = transformPositionX;
}
}
或延迟执行
private float timeSinceLastCalled;
private float delay = 1f;
void Update()
{
timeSinceLastCalled += Time.deltaTime;
if (timeSinceLastCalled > delay)
{
ExampleGarbageGeneratingFunction();
timeSinceLastCalled = 0f;
}
}
或集合再分配
void Update()
{
List myList = new List();
PopulateList(myList);
}
private List myList = new List();
void Update()
{
myList.Clear();
PopulateList(myList);
}
使用对象池
常见原因
-
String 分配,没改变一次String就重新创建一个,删除原有String
- 使用String 缓存
- text组件使用 拆分string变化和非变部分
- System.Text.StringBuilder 不会分配空间
- 移除Debug.Log()
public Text timerText; private float timer; void Update() { timer += Time.deltaTime; timerText.text = "TIME:" + timer.ToString(); }
public Text timerHeaderText; public Text timerValueText; private float timer; void Start() { timerHeaderText.text = "TIME:"; } void Update() { timerValueText.text = timer.toString(); }
DEBUG log
#if … #endif
public static class Logger { [Conditional("ENABLE_LOGS")] public static void Debug(string logMsg) { UnityEngine.Debug.Log(logMsg); } }
-
Unity Function
方法调用时产生局部缓存
void ExampleFunction() { Vector3[] meshNormals = myMesh.normals; for (int i = 0; i < meshNormals.Length; i++) { Vector3 normal = meshNormals[i]; } }
GameObject.name or GameObject.tag 比较
private string playerTag = "Player"; void OnTriggerEnter(Collider other) { bool isPlayer = other.gameObject.CompareTag(playerTag); }
可使用 Input.GetTouch() 和 Input.touchCount 替代 Input.touches 或者用 Physics.SphereCastNonAlloc() 替代 Physics.SphereCastAll()
值类型装箱
-
Coroutines
//装箱了0 产生内存垃圾 yield return 0; yield return null; //每次循环产生一次垃圾 while (!isComplete) { yield return new WaitForSeconds(1f); } WaitForSeconds delay = new WaitForSeconds(1f); while (!isComplete) { yield return delay; }
-
foreach
void ExampleFunction(List listOfInts) { foreach (int currentInt in listOfInts) { DoSomething(currentInt); } }
void ExampleFunction(List listOfInts) { for (int i = 0; i < listOfInts.Count; i ++) { int currentInt = listOfInts[i]; DoSomething(currentInt); } }
函数引用
LINQ and Regular Expressions
代码结构
结构体
结构体为值类型,包含引用类型变量会导致收集器检查真个结构体,占用更多时间
public struct ItemData
{
public string name;
public int cost;
public Vector3 position;
}
private ItemData[] itemData;
private string[] itemNames;
private int[] itemCosts;
private Vector3[] itemPositions;
引用查找&角标查找
return id更省
手动回收
System.GC.Collect();