Unityのコルーチン機能を使う

つぶやく

Unityにはコルーチンという便利な機能があります。

(Unityの機能というか.Netの機能をUnityが使っているというか…)

コルーチンとは?

コルーチン自体はUnity特有のものではありません。

マイクロスレッドやファイバー等といった名前でいろんなところで利用されています。

例えば、LuaスクリプトやPythonなんかにも導入されています。

さっくりいうと

さっくりコルーチンをいうと、これは関数(メソッド)を任意の場所で中断/再開する機能です。

メリットは大きく2点あります。

  1. 複数の処理を疑似並列できる。
  2. 状態を関数内で保持できる。

疑似並列できる

本来関数は1つずつしか実行できません。

とあれば1が終われば2、2が終われば3と順番に実行されます。

この123を同時に実行するにはマルチスレッドを利用する必要があります。

コルーチンでは完全に並列ではありませんが、Function1の途中まで→Function2の途中まで→Function3の途中まで→Function1の続きという風な流れで疑似的に並列処理を実現できます。

microthread

(※シーケンス図っぽい図ですが本来のシーケンス図ではないです。)

OSの疑似マルチスレッドと同様ですね。

状態を関数内で保持できる

状態遷移するような処理では

(OOPしてなかったりであまりいい処理ではないですが)このような形になります。

このstateはクラスのメンバ変数であったり、グローバル変数であったりに保持し続ける必要があります。

コルーチンではこれを

のようなイメージで実装できます。(※これはイメージです)

stateをどこかに持つ必要がなくなり、処理の流れもわかりやすくなりました。

前者の疑似並列では通信処理やロード処理といった時間がかかるけど画面が止まっては困る場合なんかに便利です。

後者の状態保持ではアニメーション処理等のゲームシステムとは独立した状態を持った処理に便利です。

Unityでのコルーチンの実装

先ほどはイメージでしたが、実際にUnityでどのように利用するかを解説します。

Unityのコルーチンは基本的にMonoBehaivourを継承したクラスでしか使えません。

コルーチンの定義

コルーチンの定義はIEnumeratorを返り値に持ち、yeildステートメントが関数内に含まれる事が条件です。

返り値は必ずIEnumeratorでなければいけません。

(※UnityではジェネリクスのIEnumerator<T>はダメです。)

yield

yieldで関数の処理が一度中断されます。

Unityではyieldの後ろの内容によりどのタイミングで処理が再開されるかが決まります。

代表的なものは

  • yield return new WaitForEndOfFrame();
    • 次のフレームに再開します。
  • yield return new WaitForSeconds(秒数);
    • 指定秒数後に再開します。
  • yield return new WaitUntil(再開条件);
    • 再開条件に指定した関数がtrueを返すと再開します。
  • yield return new WaitWhile(待機条件);
    • 待機条件にした関数がfalseを返すと再開します。
  • yield break;
    • 関数は再開されずにそこで終わります。
  • yield return StartCoroutine();
    • 別のコルーチンを新たに実行しそれが終わるまで中断します。
  • yield return 一部非同期オブジェクト;

です。

例1

例2

一部非同期オブジェクト

一部非同期オブジェクトと書きましたが、これはWWWやAssetBundleのLoadAssetAsyncの

返り値であるAssetBundleRequest等です。

といった具合で、一部のUnityのオブジェクトはyeild returnする事でその処理が終わるまでコルーチンを中断することが出来ます。

コルーチンの実行

コルーチンの実行は2通りあります

タイプ1

基本はこちらのタイプを利用します。

普通に関数を呼び出して、その返り値であるIEnumeratorをStartCoroutineに渡します。

最初のyieldまではこのStartCoroutineのタイミングで実行されます。

タイプ2

文字列で関数名を指定するタイプです。こちらはStartCoroutineの第二引数にコルーチンに渡す引数を1つまで指定できます。

こちらも同じく最初のyieldまではこのStartCoroutineのタイミングで実行されます。

2つの違い

この2つの違いは

タイプ1 タイプ2
引数の個数 任意の個数 0か1つ
コルーチンの定義箇所 特に制限なし

(返り値のIEnumeratorを渡しさえすればよい)

StartCoroutineを呼ぶMonoBehaivourのメンバ関数
後から個別停止

(StopCoroutine)

コルーチン関数の返り値(IEnumerator)を指定 関数名を指定

くらいです。

タイプ2は個別の停止(StopCoroutine)時がお手軽という程度しかメリットがないので、タイプ1を利用で良いと思います。

タイプ1をStopCoroutineするためには

のようにどこかにコルーチンの返り値のIEnumeratorを保持する必要が出て来るため少し使いづらいです。

コルーチンの実行イメージ

coroutine-image

Unityの処理の流れがゲームループです。大体1/60秒ごとに一度実行されます。

右側がコルーチンです。yieldを検知してUnityによって自動的に各フレームに処理が分散します。

注意点

スパゲティに注意

疑似とはいえ並列処理はコードが入り組みスパゲティになりがちです。

また、処理の順番もわかりにくくなりがちです。分かりやすいコードを意識しましょう。

GameObjectのActiveに注意

コルーチンはGameObjectがアクティブでない状態では動きません。

具体的には非アクティブになったタイミングでそのGameObjectの実行されているコルーチンがすべて停止します。

また、非アクティブ中のタイミングでのStartCoroutineの呼び出しはエラーが発生します。

この停止というのは一時中断ではなく停止です。この後再度アクティブになったとしてもコルーチンは再開されません

完全並列ではない

あくまで疑似並列であり、完全並列ではありません。

その為例えば以下のような無限ループが発生するとアプリ全体が止まります。

以下は(おそらく)無限ループではありませんが予期せぬ動作を起こします。

.Netでのコルーチン

Unityでの話をしましたが、最後にちょっと本来のC#としての機能のコルーチンの書き方を紹介します。

と言っても古いやり方ですが。

最初
1
2番目
2
3番目
3
最後

このようになります。

通常のIEnumeratorと全く同じように使えます。MoveNextでコルーチンが次のyeildまで進みます。Currentにはyieldでreturnしている値が取得できます。

スポンサーリンク