概要
UnityのCoroutineは基本StopCoroutine(),StopAllCoroutineAll()で一時停止する仕様になっているが、ネストしたCoroutineも含めて親のCoroutineからStopCoroutineするとネストしたCoroutineが終了してからしか一時停止しません。
実例
例えば以下のようなコードで
IEnumerator _coroutine;
void Start()
{
_coroutine = Coroutine();
StartCoroutine(_coroutine);
}
IEnumerator Coroutine()
{
yield return NestCoroutine();
}
IEnumerator NestCoroutine()
{
var time = 0f;
while(time < 2f){
yield return null;
time += Time.deltaTime;
}
}
Coroutineを実行中に以下のように親のCoroutineをStopしようとしてもNestCoroutineの終了を待ってからしかStopしてくれません。
void Stop()
{
StopCoroutine(_coroutine);
}
これが結構不便で、例えばゲームのポーズを実装したい場合にStopAllCoroutineで全部停止して済むなら良いのですが、特定のCoroutineだけ止めたい場合もあるわけで。
こういう場合に親のCoroutineをStopCoroutineさせるだけではネストしたCoroutineが止まってくれないので、ネストした全てのCoroutine中にフラグを設定してフラグで制御するとか、すべてのネストしたCoroutineを変数に格納しておいてStopCoroutineするなど面倒な制御を要求されます。
こういった細かい制御をしたい場合IEnumeratorを自前でMoveNext()していく方法があります。
MoveNestでCoroutineを実行していく場合、以下のように書きます。UpdateでMoveNextを回して行くのが一般的ですが、あえてStartCoroutine中でMoveNextを回していきます。理由は後述。
void Start()
{
StartCoroutine(TaskCoroutine(Coroutine()));
}
IEnumerator TaskCoroutine(IEnumerator task)
{
while(true)
{
if(task.MoveNext()){
yield return null;
}
else {
break;
}
}
}
IEnumerator Coroutine()
{
yield return NestCoroutine();
}
IEnumrator NestCoroutine()
{
yield return null;
}
ただし、MoveNext()を単純に回して行くだけだとネストしたコルーチンには対応できません。
ではどうするかというと、実はネストしたコルーチンの情報はIEnumeratorのCurrentの部分に含まれているのでこれをまたMoveNext()で回して行けばよいのです。
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
}
後は、currentの型を判断してネストしたコルーチンに何が含まれているかで場合分けして処理を分けます。typeがIEnumeratorの場合はネストしたコルーチンなので、再帰的にMoveNextを実行します。
前述したようにStartCoroutineでMoveNextを実行しているのは、IEnumerator以外の型の場合はUnity側に処理を任せるためです。
後は、唯一設定したフラグ_isPauseを制御することで、ネストしたコルーチン含めて一時停止、一時停止解除することが出来ます。
bool _isPause = false
void Start()
{
var task = Coroutine();
StartCoroutine(TaskCoroutine(task));
}
IEnumerator TaskCoroutine(IEnumerator task)
{
while (true)
{
var ie = MoveNext(task);
yield return ie;
if (ie.Current is bool)
{
if (!(bool)ie.Current)
{
break;
}
}
}
}
IEnumerator MoveNext(IEnumerator task)
{
if (_isPause)
{
yield return null;
}
else
{
if (task.MoveNext())
{
var current = task.Current;
if (current == null)
{
yield return null;
}
else
{
var type = current.GetType();
//IEnumeratorはcurrent isで判断する
if (current is IEnumerator)
{
while (true)
{
//currentがIEnumeratorの場合再帰的に実行
var ie = MoveNext((IEnumerator)current);
yield return ie;
if (ie.Current is bool)
{
if (!(bool)ie.Current)
{
break;
}
}
}
}
//IEnumerator以外はcurrent.GetType()で判断する
//下記は説明用の分岐。
else if (type == typeof(Coroutine))
{
yield return current;
}
else
{
yield return current;
}
}
}
else
{
yield return false;
}
}
}
IEnumerator Coroutine()
{
yield return NestCoroutine();
}
IEnumerator NestCoroutine()
{
var time = 0f;
while (time < 2f)
{
yield return null;
time += Time.deltaTime;
Debug.Log(time);
}
}
補足
上記のコードを見ると分かりますが一時停止できるのはネストしたコルーチンがyield return (IEnumerator関数)の時だけで、yield return new WaitForSecound(1.0f)とか、yield return StartCoroutine(Coroutine()) とかの場合には無理です。てか、StopCoroutineがそもそもネストしたコルーチンも含めてStopできないのが問題なのです。