關於 web service, unity, blogger 等軟體工程筆記

Unity Coroutine 使用筆記

Edit icon 沒有留言
Unity

最近有人問為什麼 Nested coroutine 的機制與問題,為什麼會是需要 yield return StartCoroutine(...),而不能省略 StartCoroutine?在久遠的 Unity3.x 時代,印象中得呼叫該函數才能夠正常運作。因此重新看了一下 Unity 文件,並且實際測試來確認是否在 Unity5.x 以後已經有所調整,以及整理成一個簡單的筆記。

Note: 使用 Unty5.6.1 測試

關於 Coroutines

Nested Coroutine 階層執行

以往經驗以為階層執行得使用 Coroutine 在包一層才能執行,執行 A 並等待 B 結束:

using System.Collections;
using UnityEngine;

public class CorutineTest : MonoBehaviour
{
   void Start()
   {
      this.StartCoroutine(A());
   }

   IEnumerator A()
   {
      ...
      yield return StartCoroutine(B());     // 這裡可以不需要 StartCoroutine
      ...
   }

   IEnumerator B()
   {
      ...
      yield return new WaitForSeconds(5.0f);
      ...
   }
}

但實際上測試,在啟用 B 不需要再次呼叫 StartCoroutine 也能夠正常執行不會中斷。

執行 Nested Coroutine 的流程

執行 Nested Coroutine 的流程

Parallel Coroutine 平行執行

如果需要分成多個 Coroutines 執行,之後在等待其執行結束的話,就得使用 StartCoroutine:

using System.Collections;
using UnityEngine;

public class CorutineTest : MonoBehaviour
{
   void Start()
   {
      this.StartCoroutine(A());
   }

   IEnumerator A()
   {
      var b = StartCoroutine(B());
      var c = StartCoroutine(C());
      ...
      yield return b;
      yield return c;
      ...
   }

   IEnumerator B()
   {
      ... // Do jobB
   }

   IEnumerator C()
   {
      ... // Do jobC
   }
}

這常用於遊戲初始化的流程,A 為主要的初始化流程,B 負責載入場景, C 負責載入網路資料,之後 A 在處理當該兩項工作完成後,要繼續進行初始化的工作。

執行 Parallel Coroutine 的流程

執行 Parallel Coroutine 的流程

Unity 內建的 YieldInstruction

  • WaitForSeconds

    等待指定的遊戲時間 (遊戲流逝時間可用 Time.scale 調整)

    IEnumerator Example()
    {
       print(Time.time);
       yield return new WaitForSeconds(5);
       print(Time.time);
    }
    
  • WaitForSecondsRealtime

    等待指定的真實時間 (現實時間不受到 Time.scale 影響)

    IEnumerator Example() 
    {
       print(Time.realtimeSinceStartup);
       yield return new WaitForSecondsRealtime(5);
       print(Time.realtimeSinceStartup);
    }
  • WaitForFixedUpdate

    等待下次的 FixedUpdate (物理運算) 後再執行

  • WaitForEndOfFrame

    等待這個 Frame 繪製完但還沒有顯示的時間點再執行。可以用來取得繪製結果做某些事情。

  • WaitUntil

    等到傳入的 delegate 滿足條件回傳 true 後再執行,例如以下等待 AudioSource 播放完音訊的例子:

    IEnumerator Example()
    {
       yield return new WaitUntil(() => { return !audioSource.isPlaying; });
    }
  • WaitWhile

    跟 WaitUntil 差不多的概念,只是改成傳入的 delegate 滿足條件回傳 false 後再執行,例如修改上述的例子:

    IEnumerator Example()
    {
       // 如果音訊在播放就持續的等
       yield return new WaitWhile(() => { return audioSource.isPlaying; });
    }

Extend YieldInstruction 擴充功能

兩種方法,繼承以及實作 Unity 所定義的 UnityEngine.CustomYieldInstruction 類別,或者直接實作 System.Collection.IEnumerrator,例如實作等待 Animator 切換到指定狀態的 YieldInstruction:

using System.Collections;
using UnityEngine;

public struct WaitForAnimatorState : IEnumerator
{
   Animator animator;
   string stateName;

   public WaitForAnimatorState(Animator animator, string stateName)
   {
      this.animator = animator;
      this.stateName = stateName;
   }

   bool IEnumerator.MoveNext()
   {
      return !animator.GetCurrentAnimatorStateInfo(0).IsName(this.stateName);
   }

   object IEnumerator.Current
   {
      get
      {
         return null;
      }
   }

   void IEnumerator.Reset()
   {
   }
}

使用以下方式操作,等待指定 Animator 切換到 Attack 狀態再執行:

IEnumerator Example()
{
   yield return new WaitForAnimatorState(animator, "Attack");
}

其實可不用建立 WaitForAnimatorState,改用 WaitUntil:

IEnumerator Example()
{
   yield return new WaitUntil(() => { return animator.GetCurrentAnimatorStateInfo(0).IsName("Attack"); });
}

抑或是使用最簡單的方式:

IEnumerator Example()
{
   while (!animator.GetCurrentAnimatorStateInfo(0).IsName("Attack"))
   {
      yield return null;
   }
}

Reference

沒有留言: