Unity學(xué)習(xí)—腳本優(yōu)化Tips

本文原地址: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)試

  1. SendMessage() and BroadcastMessage()消耗大,盡量直接對(duì)象調(diào)用或者使用Events或Delegates

  2. Find() 使用引用持有、Inspector面板引用、使用專用管理類

  3. Transform 修改對(duì)象transform屬性會(huì)觸發(fā)子對(duì)象OnTransformChanged,子對(duì)象越多消耗越大。可將需要頻繁修改的transform屬性存入Vector3對(duì)象進(jìn)行計(jì)算,再將Vector賦值回transform

  4. Transform.position每次調(diào)用會(huì)計(jì)算一次,Transform.localPosition調(diào)用則直接取值

  5. Update() 空方法也會(huì)占用CPU調(diào)用資源,所以當(dāng)Update為空時(shí)直接刪除

  6. Vector2 and Vector3 將向量計(jì)算替換為基本類型計(jì)算,例 Vector3.magnitude、 Vector3.sqrMagnitude

  7. 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

  1. 堆區(qū)無足夠控件分配
  2. 自動(dòng)周期執(zhí)行
  3. 手動(dòng)執(zhí)行

垃圾收集器執(zhí)行步驟

  1. 檢查堆區(qū)對(duì)象
  2. 查找所有引用
  3. 標(biāo)記空閑對(duì)象
  4. 還原堆空間

問題

  1. 堆區(qū)對(duì)象越多執(zhí)行時(shí)間越長(zhǎng)
  2. 錯(cuò)誤執(zhí)行時(shí)機(jī),如 CPU高占用時(shí)調(diào)用
  3. 堆碎片化,可分配空間不足,導(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ì)象池

常見原因

  1. 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 … #endif
    
    public static class Logger {
    
        [Conditional("ENABLE_LOGS")]
    
        public static void Debug(string logMsg) {
    
            UnityEngine.Debug.Log(logMsg);
    
        }
    
    }
    
  2. 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()

  3. 值類型裝箱

  4. 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;
    }
    
    
  5. 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);
        }
    }
    
  6. 函數(shù)引用

  7. 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();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容