本文原地址:Unity學(xué)習(xí)—腳本優(yōu)化Tips
代碼編譯原理
Unity 首先將腳本編譯為中間語言 CIL (Common Intermediate Language ),CIL可再被編譯為不同的原生語言。然后對(duì)不同的平臺(tái) Unity 采用不同的編譯方式,分為運(yùn)行前 AOT(Ahead of Time) 和運(yùn)行時(shí) JIT(Just in Time) 編譯
C# ==> CIL ==> AOT(build) / JIT(device)
源代碼編譯為托管代碼(managed code),托管代碼編譯為原生代碼,托管運(yùn)行時(shí)(managed runtime)管理整合,負(fù)責(zé)內(nèi)存自動(dòng)管理、安全檢查等
程序執(zhí)行時(shí),CPU在引擎代碼和托管代碼之間進(jìn)行安全檢查,數(shù)據(jù)格式轉(zhuǎn)換稱為編組(marshalling),存在消耗但不是特別大
影響代碼運(yùn)行原因
- 糟糕代碼結(jié)構(gòu)
方法多次無效調(diào)用 - 調(diào)用其他不必要大開銷代碼
引擎代碼和原生代碼的相互調(diào)用 - 不必要時(shí)機(jī)調(diào)用
過早進(jìn)行物理判斷、畫面判斷 - 代碼本身占用過高
大量計(jì)算
優(yōu)化方式
避免大量調(diào)用Unity API
利用profile監(jiān)測(cè)游戲調(diào)試
SendMessage() and BroadcastMessage()消耗大,盡量直接對(duì)象調(diào)用或者使用Events或Delegates
Find() 使用引用持有、Inspector面板引用、使用專用管理類
Transform 修改對(duì)象transform屬性會(huì)觸發(fā)子對(duì)象OnTransformChanged,子對(duì)象越多消耗越大。可將需要頻繁修改的transform屬性存入Vector3對(duì)象進(jìn)行計(jì)算,再將Vector賦值回transform
Transform.position每次調(diào)用會(huì)計(jì)算一次,Transform.localPosition調(diào)用則直接取值
Update() 空方法也會(huì)占用CPU調(diào)用資源,所以當(dāng)Update為空時(shí)直接刪除
Vector2 and Vector3 將向量計(jì)算替換為基本類型計(jì)算,例 Vector3.magnitude、 Vector3.sqrMagnitude
Camera.main 同F(xiàn)ind()
條件判斷前置
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]);
}
}
}
僅在展示變量變化時(shí)調(diào)用展示代碼
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);
}
控制代碼調(diào)用幀數(shù)
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<Renderer>();
ExampleFunction(myRenderer);
}
private Renderer myRenderer;
void Start()
{
myRenderer = GetComponent<Renderer>();
}
void Update()
{
ExampleFunction(myRenderer);
}
使用適當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu)
減少垃圾收集
使用對(duì)象池,如敵人、子彈
剔除 Culling
void Update()
{
UpdateTransformPosition();
UpdateAnimations();
}
private Renderer myRenderer;
void Start()
{
myRenderer = GetComponent<Renderer>();
}
void Update()
{
UpdateTransformPosition();
if (myRenderer.isVisible)
{
UpateAnimations();
}
}
Level of detail
細(xì)節(jié)等級(jí)
垃圾處理優(yōu)化
簡(jiǎn)介
Unity 核心代碼為手動(dòng)內(nèi)存管理,開發(fā)代碼自動(dòng)內(nèi)存管理同棧區(qū)堆區(qū)
棧區(qū)回收立即銷毀、堆區(qū)回收會(huì)再在重新分配時(shí)清空
垃圾收集器標(biāo)記和銷毀堆區(qū)內(nèi)存,收集器定期清理堆
垃圾收集器執(zhí)行時(shí)機(jī)Garbage Collector
- 堆區(qū)無足夠控件分配
- 自動(dòng)周期執(zhí)行
- 手動(dòng)執(zhí)行
垃圾收集器執(zhí)行步驟
- 檢查堆區(qū)對(duì)象
- 查找所有引用
- 標(biāo)記空閑對(duì)象
- 還原堆空間
問題
- 堆區(qū)對(duì)象越多執(zhí)行時(shí)間越長(zhǎng)
- 錯(cuò)誤執(zhí)行時(shí)機(jī),如 CPU高占用時(shí)調(diào)用
- 堆碎片化,可分配空間不足,導(dǎo)致收集器頻繁調(diào)用
方案
提前緩存
void OnTriggerEnter(Collider other)
{
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
ExampleFunction(allRenderers);
}
private Renderer[] allRenderers;
void Start()
{
allRenderers = FindObjectsOfType<Renderer>();
}
void OnTriggerEnter(Collider other)
{
ExampleFunction(allRenderers);
}
不在頻繁調(diào)用的方法里分配
void Update()
{
ExampleGarbageGeneratingFunction(transform.position.x);
}
改為條件執(zhí)行
private float previousTransformPositionX;
void Update()
{
float transformPositionX = transform.position.x;
if (transformPositionX != previousTransformPositionX)
{
ExampleGarbageGeneratingFunction(transformPositionX);
previousTransformPositionX = transformPositionX;
}
}
或延遲執(zhí)行
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);
}
使用對(duì)象池
常見原因
-
String 分配,沒改變一次String就重新創(chuàng)建一個(gè),刪除原有String
- 使用String 緩存
- text組件使用 拆分string變化和非變部分
- System.Text.StringBuilder 不會(huì)分配空間
- 移除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 … #endifpublic static class Logger { [Conditional("ENABLE_LOGS")] public static void Debug(string logMsg) { UnityEngine.Debug.Log(logMsg); } } -
Unity Function
方法調(diào)用時(shí)產(chǎn)生局部緩存
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 產(chǎn)生內(nèi)存垃圾 yield return 0; yield return null; //每次循環(huán)產(chǎn)生一次垃圾 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); } } 函數(shù)引用
LINQ and Regular Expressions
代碼結(jié)構(gòu)
結(jié)構(gòu)體
結(jié)構(gòu)體為值類型,包含引用類型變量會(huì)導(dǎo)致收集器檢查真?zhèn)€結(jié)構(gòu)體,占用更多時(shí)間
public struct ItemData
{
public string name;
public int cost;
public Vector3 position;
}
private ItemData[] itemData;
private string[] itemNames;
private int[] itemCosts;
private Vector3[] itemPositions;
引用查找&角標(biāo)查找
? return id更省
手動(dòng)回收
System.GC.Collect();