1. 程式人生 > 實用技巧 >Unity 協程等待物件的生命週期

Unity 協程等待物件的生命週期

  Unity系統提供的協程等待有下面幾個 :

    yield return new WaitForFixedUpdate();
    yield return new WaitForEndOfFrame();
    yield return new WaitForSeconds(1.0f);
    yield return new WaitForSecondsRealtime(1.0f);

  一般情況下我們對協程的生命週期不是很關心, 因為是非同步模式的 ( 雖然它不是非同步的 ), 不過對於一個需要返回的等待物件, Unity 提供的是一個 class 物件, 比如一個迴圈等待的協程, 它就會產生很多的 GC 物件, 像下面這樣 :

    IEnumerator TestCoroutine1()
    {
        while(true)
        {
            // ......
            yield return new WaitForEndOfFrame();
            // ......
        }
    }

  而它又只是一個物件, 沒有附帶其它資訊, 那麼這個東西其實是可以複用的, 直接給它一個靜態物件也是一樣的 :

    public static readonly WaitForEndOfFrame WaitForEndOfFrame = new
WaitForEndOfFrame(); IEnumerator TestCoroutine1() { while(true) { yield return WaitForEndOfFrame; Debug.Log(Time.frameCount); } }

  然後很多人發現對於 WaitForEndOfFrame 其實返回的物件是空時 ( null ), 得到的結果也一樣, 可是其實他們執行程式碼所在的生命週期是不同的, 可以看看下面的測試結果 :

using System;
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Test : MonoBehaviour { int id = 0; void Awake() { StartCoroutine(TestCoroutine()); } void Update() { Debug.Log("Update :: " + id++); } void LateUpdate() { Debug.Log("LateUpdate :: " + id++); } private void OnPostRender() { Debug.Log("OnPostRender :: " + id++); } IEnumerator TestCoroutine() { Debug.Log("Coroutine Start :: " + id++); int i = 0; while(i++ < 3) { Debug.Log("Coroutine Tick : " + i + " :: " + id++); yield return null; Debug.Log("Coroutine End : " + i + " :: " + id++); } this.enabled = false; } }

  這裡使用 null 作為協程返回, 添加了 Update, LateUpdate, OnPostRender 等生命週期, 得到的結果如下 :

  可見每次 yield 等待之後的程式碼呼叫都是在Update之後, LateUpdate之前呼叫的, 它插入的生命週期就在這裡, 而WaitForEndOfFrame 的就不同了, 就像描述的一樣, 是在"一幀"之後才會呼叫 :

    IEnumerator TestCoroutine()
    {
        Debug.Log("Coroutine Start :: " + id++);
        int i = 0;
        while(i++ < 3)
        {
            Debug.Log("Coroutine Tick : " + i + " :: " + id++);
            yield return new WaitForEndOfFrame();
            Debug.Log("Coroutine End : " + i + " :: " + id++);
        }
        this.enabled = false;
    }

  可以看到插入的生命週期在相機完成渲染之後, 其實是在所有生命週期之後, 所以需要根據實際需求選擇正確的返回, 比如你要獲取當前幀相機的正確渲染結果, 就需要使用 WaitForEndOfFrame, 而你如果想要改變當前幀的渲染結果, 你就要使用 null 返回, 否則就變成獲取到上一幀的渲染結果和改變下一幀的渲染了, 雖然影響不大...

  而 WaitForFixedUpdate 這個在物理幀裡的顯然使用的就很少了, 它跟邏輯跟渲染都不搭嘎.

  而WaitForSeconds 跟WaitForSecondsRealtime 有個決定性的不同,WaitForSeconds 跟WaitForEndOfFrame 一樣只是個簡單物件, 而WaitForSecondsRealtime 有自己的計時器 :

  所以測試結果 WaitForSecondsRealtime 是無法使用全域性物件的 :

    IEnumerator TestCoroutine()
    {
        var WaitForSecondsRealtime = new WaitForSecondsRealtime(1.0f);
        Debug.Log("Coroutine Start :: " + id++);
        int i = 0;
        while(i++ < 3)
        {
            Debug.Log("Coroutine Tick : " + i + " :: " + id++);
            yield return WaitForSecondsRealtime;
            Debug.Log(Time.realtimeSinceStartup);
            Debug.Log("Coroutine End : " + i + " :: " + id++);
        }
        this.enabled = false;
    }

  它在第一次成功返回之後, 每幀都是成功返回, 沒有意義...

  而 WaitForSeconds 還是正常的 :

    IEnumerator TestCoroutine()
    {
        var WaitForSeconds = new WaitForSeconds(1.0f);
        Debug.Log("Coroutine Start :: " + id++);
        int i = 0;
        while(i++ < 3)
        {
            Debug.Log("Coroutine Tick : " + i + " :: " + id++);
            yield return WaitForSeconds;
            Debug.Log(Time.realtimeSinceStartup);
            Debug.Log("Coroutine End : " + i + " :: " + id++);
        }
        this.enabled = false;
    }