たまに使いたくなるが、いまいち理解できていないもののひとつが、コルーチン(Coroutine)。
ということで、いくつかメモ書きを。
コルーチン(Coroutine)とは何か?
Coroutineとは何だろうか? Wikipediaには次のようにある。
「コルーチン(英: co-routine)とはプログラミングの構造の一種。サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。 サブルーチンと異なり、状態管理を意識せずに行えるため、協調的処理、イテレータ、無限リスト、パイプなど、継続状況を持つプログラムが容易に記述できる。」
簡潔に言うと、コルーチンとは、プログラムを任意の箇所で中断・再開するしくみ、と言っていいだろう。
Coroutineはどう記述するか?
Coroutineとして中断・再開するメソッドは、
- IEnumerator型を戻り値とし
- 中断・再開する場所に、
yield return 「真偽判定結果」
を置く
が基本形。
このメソッドを、
StartCoroutine(メソッドを代入したIEnumerator型メソッド、または、それを代入した変数)
または、StartCoroutine("メソッド名の文字列")
として呼び出す。
前者の記述方法は、メソッドに引数がある場合・ない場合の両方に使えるが、後者は引数がない場合にしか使えない。
Coroutineはどんな時に使うか?
1.一定時間処理を止めたいとき
Unityでも他のアプリケーションでも、ループの中で、ある処理を一定時間止めたい、というのはよくある要請。
この場合は、ループ(for, while)の中の停止したい場所にyield returnを置き、真偽判定条件として、WaitForSecondsクラスを設定する。
次の例では、0.2秒ごとにアルファ値が0.1小さくなる。
IEnumerator Fade() { for (float f = 1f; f >= 0; f -= 0.1f) { Color c = renderer.material.color; c.a = f; renderer.material.color = c; yield return new WaitForSeconds(0.2f); //毎ループここで0.2秒停止する } } void Update() { if (Input.GetKeyDown("f")) { StartCoroutine("Fade"); } }
2.呼び出されるごとに、処理を変えたい時
yield return
を複数個記述すれば、呼び出されるごとに前のyield returnの次から処理が続けられ、次にあたったyield return
で処理が停止する。これによって戻り値を順番に変えることができる。
IEnumerator coRoutine(){ msgText.text = "Andy "; yield return null; msgText.text = "Bob"; yield return null; msgText.text = "Carter "; yield return null; msgText.text = "Dave"; }
3.あるメソッドの(for)ループを、Updateループに同期して進めたいとき
UnityでのCoroutineの特徴的な使い方としては、Updateループ外のメソッドのループをUpdateフレームに対応させて進ませる、というのがある。次は、Unityマニュアルにある具体例。
IEnumerator Fade() { for (float f = 1f; f >= 0; f -= 0.1f) { Color c = renderer.material.color; c.a = f; renderer.material.color = c; yield return null; } } void Update() { if (Input.GetKeyDown("f")) { StartCoroutine("Fade"); } }
yield return null
は、ここで処理を中断し、次のフレームで再開するための記述法。これにより、Updateループが1回進むごとに、Fade()内のforループも1回進むことになる。
この例では、yield return
の真偽判定に'null'が設定されている。これは、単にyield return
と書いてもいい。
「ここで制御を呼び出し側に戻すけど、次に呼び出されたら、無条件にこの場所から再開するよ」と解釈される。
コルーチンから戻り値を受け取る
コルーチンから戻り値を受け取りたい時はどうするか?これは次を参考に!
カスタムコルーチン(Custom Coroutine)
Unity5.3から「カスタムコルーチン」が使えるようになり、yield returnの処理中断・再開条件を、自分でカスタマイズできるようになった。(関連Unityブログ)。
次はカスタムコルーチンのサンプル。指定したwaitTime秒待つメソッド(WaitForSecondsと同じ機能)の改良版。(注:ソースコードに誤りがあり訂正しました。また、WaitForSecondsRealtimeはすでにUnity本体に実装されたようです。2017/4/13)
using UnityEngine; using System.Collections; public class WaitForSecondsRealtime: CustomYieldInstruction { private float waitTime; public override bool keepWaiting { get { return Time.realtimeSinceStartup < waitTime; } } public void WaitForSecondsRealtime(float time) { waitTime = Time.realtimeSinceStartup + time; } }
カスタムコルーチン・クラスのポイントは、
- CustomYieldInstructionクラスを継承する
- public bool keepWaitingをoverrideし、中断・再開の条件を記述する
の2点。
using UnityEngine; using System.Collections; public class CoTest : MonoBehaviour { private IEnumerator coroutine; // Use this for initialization IEnumerator Sub(float delay = 1.0f) { Debug.Log("Sub Before "+Time.realtimeSinceStartup); yield return new WaitForSecondsRealtime(delay); Debug.Log("Sub After" + Time.realtimeSinceStartup); } // Update is called once per frame private void Start() { Debug.Log("Start Before "+Time.realtimeSinceStartup); coroutine = Sub(3.0f); StartCoroutine(coroutine); Debug.Log("Start After " + Time.realtimeSinceStartup); } }
Start()の中のStartCoroutine(coroutine);
でコルーチンを呼び出している。(変数coroutineは、そのひとつ前の行でSub(3.0f
として定義している)
これを実行した結果(Console出力):"Sub Before"~"Sub After"の間が約3秒空いていることがわかる。
なお、コルーチン呼び出し側の処理は止まらないことに注意。すなわち、呼び出し側では、"Start Before"から"Start After"は、即座に実行される。
コルーチンの実体
コルーチンは、コレクション(Collections)と深くつながっている。
Collectionsの要素をひとつずつ取り出して処理する場合、foreach(){...}を使う。このforeachループで次々に値を取得できるようにしているのが、IENumerable
インターフェースである。
IEnumerable
インファーフェースを実装したクラスに、IEnumerator
を戻り型とするGetIEnumerator
メソッドを実装することで、列挙を可能にしている(ややこしい!)。
IEnumerableインターフェースには、Collectionsの現在の要素を取得したり、次の要素に進めたり(MoveNext)、最初の要素に戻ったり(Reset)する機能が定義されている。
そして、yieldは、Collectionsの次の要素をforeachで処理できるようにするためのものである。
つまり、IEnumerable/IEnumerator/yieldという組み合わせは、もともとSystem.Collectionsに列挙可能性を付加する機能で、それをコルーチンの遅延実行に利用している、と考えてもいいかな。あるいは、その逆に、遅延実行機能を、Collectionsの列挙可能性に利用した、でもいいのかも。