コルーチンを自前で動かしたい
コルーチンはMonoBehaviourを継承していればStartCoroutineで動かすことができます。しかし、MonoBehaviourを継承していなくても使いたい場面は割と多いです、多分。
MonoBehaviourなしでコルーチンを動かす方法は調べるとそれなりに出てきますが、そもそもコルーチンで使われているIEnumeratorとはなんぞ?から始めたい所存です。
IEnumeratorインターフェース?
MSDNを見てみるとシンプルに書かれています。
非ジェネリック コレクションに対する単純な反復処理をサポートします。
IEnumeratorインターフェースは反復処理を実現するためのインターフェースになります。
コルーチンはUnityが反復処理をいい感じに実行してくれてる仕組みというわけですね。
反復処理を作って動かしてみる
とりあえず物は試しということでUnityで適当な反復処理を作ってみます。
わかりやすくデバッグログも出してこんな感じに。
public IEnumerator Coroutine() {
Debug.Log("Coroutine 1");
yield return 1;
Debug.Log("Coroutine 2");
yield return 2;
}
こいつをこんな感じで動かしてみます
public void Sample() {
var iEnumerator = Coroutine();
while (iEnumerator.MoveNext()) {
Debug.Log($"Coroutine Return {iEnumerator.Current}");
}
}
Coroutine 1
Coroutine Return 1
Coroutine 2
Coroutine Return 2
おもったよりかんたん!
何をやっているの?
まずは反復処理についてです。何気なく使っているyieldですが、yieldを含むブロックのことをイテレータブロックと言います。
大雑把に説明すると「1回の処理でここまでやってね」というブロックになります。yieldには値を返すreturnと処理を抜けるbreakの2つのキーワードがあります。
次にMoveNextです。これはイテレータを次に進ませます。イテレータが進んだ場合はtrueを返すため、whileを使うとイテレータを全て進めることができます。
Currentは反復処理が返した値を保持しているプロパティです。yield returnで返した値がそのまま入ってくる感じですね。
object型なので色んなものを返せます。
public IEnumerator Coroutine() {
yield return 1;
yield return 9.6;
yield return "ほげえ";
}
Coroutine Return 1
Coroutine Return 9.6
Coroutine Return ほげえ
また、Resetに関してですが、こちらは自前で実装する必要があります(実装してないとNotSupportedExceptionがスローされます)。
単純にIEnumeratorインターフェースで実装した反復処理メソッドをやり直したい場合は対象のメソッドを再び取得して動かします。
public void Sample() {
var iEnumerator = Coroutine();
while (iEnumerator.MoveNext()) {
Debug.Log($"Coroutine Return {iEnumerator.Current}");
}
// iEnumerator.Reset(); <- NotSupportedExceptionがスローされる
iEnumerator = Coroutine();
while (iEnumerator.MoveNext()) {
Debug.Log($"Coroutine Return {iEnumerator.Current}");
}
}
思ったよりシンプル!これならStartCoroutineなしでもなんとかなりそう・・・?
WaitFor関係は更に自前で実装しないとダメ
注意点としてWaitForSecounds等は単純にyield returnしただけではうまくいかないです。
よくあるのがyield return WaitForSecondsでカウントをするコルーチンですが、単純にMoveNextをしただけではできません。
MoveNextは1フレーム毎にイテレータを進めるというイメージが良いと思います。この辺はStartCoroutineがいい感じに処理してくれてる部分です。
反復処理の入れ子をやりたい
反復処理から反復処理を実行するような形にすればできます。実はIEnumeratorインターフェースを調べ始めたきっかけでもあります。
public IEnumerator Coroutine() {
Debug.Log("Coroutine Start");
var coroutine2 = Coroutine2();
while (coroutine2.MoveNext()) {
yield return coroutine2.Current;
}
Debug.Log("Coroutine End");
}
public IEnumerator Coroutine2() {
Debug.Log("Coroutine2 1");
yield return 1;
Debug.Log("Coroutine2 2");
yield return 2;
}
public void Sample() {
var iEnumerator = Coroutine();
while (iEnumerator.MoveNext()) {
Debug.Log($"Coroutine Return {iEnumerator.Current}");
}
}
Coroutine Start
Coroutine2 1
Coroutine Return 1
Coroutine2 2
Coroutine Return 2
Coroutine End
おしまい
何気なく使っていたコルーチンとIEnumeratorインターフェースですが、思ったよりシンプルでした。
これでもまだ触りの部分だと思いますが、当初の目的を達成しつつある程度は分かった気がしたのでとりあえずOKとしましょう。
何かあったらコメントください。
参考
++C++; // 未確認飛行 C イテレーター
WisdomSoft 処理の分割(コルーチン)
Qiita:[C#]IEnumeratorとIEnumerableを調べた