Unity Coroutine 使用筆記
最近有人問為什麼 Nested coroutine 的機制與問題,為什麼會是需要 yield return StartCoroutine(...)
,而不能省略 StartCoroutine
?在久遠的 Unity3.x 時代,印象中得呼叫該函數才能夠正常運作。因此重新看了一下 Unity 文件,並且實際測試來確認是否在 Unity5.x 以後已經有所調整,以及整理成一個簡單的筆記。
關於 Coroutines
- Coroutines 並沒有開新的線程 (Thread),在 Main thread 執行
- Coroutines yield return 通常在 Update 之後才呼叫,參考 Unity 文件 Execution Order
- Coroutines 基於 C# IEnumerator,參考之前的文章 C# IEnumerator, IEnumerable, and Yield
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
也能夠正常執行不會中斷。
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 在處理當該兩項工作完成後,要繼續進行初始化的工作。
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;
}
}
沒有留言: