Unity のコルーチンで結果を受け取る

  • 98
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Unity では非同期的な処理をコルーチンを使って書くことができます。
たとえば、

IEnumerator Hoge(){
    yield return StartCoroutine(Async());
}

IEnumerator Async(){
    Something.Run();
    while(!Something.Finished){
      yield return null;
    }
}

でもこれだと結果が受け取れません。結果を受け取るための方法には私が思いつくだけで3つあります。

  1. どちらのメソッドからも見える変数に値を代入する
  2. コールバック関数を渡して結果を返してもらう
  3. IEnumerator#Current を使用する

1. どちらのメソッドからも見える変数に値を代入する

int Result;

IEnumerator Hoge(){
    yield return StartCoroutine(Async());
    print(Result);
}

IEnumerator Async(){
    Something.Run();
    while(!Something.Finished){
      yield return null;
    }
    Result = 3535;
}

問題点

  • Result の寿命が Result を参照しているクラスだけではなく、このクラスの寿命にも影響されてしまうこと
  • Result のスコープが必要以上に広くなってしまう可能性があること
  • Async 結果を受け取るために Result を使用することを知らないといけない
  • Async を並列に走らせることができないこと

2. コールバック関数を渡して結果を返してもらう

IEnumerator Hoge(){
    int? result = null;
    yield return StartCoroutine(Async(r => result = r));
    print(result.Value);
}

IEnumerator Async(Action<int> callback){
    Something.Run();
    while(!Something.Finished){
      yield return null;
    }
    callback(3535);
}

問題点

  • ネストが増えるので読みにくくなる
  • 書き方によっては上から下に読み下せない

3. IEnumerator#Current を使用する

IEnumerator Hoge(){
    var coroutine = Async();
    yield return StartCoroutine(coroutine);
    var result = (int)coroutine.Current;
    print(result);
}

IEnumerator Async(){
    Something.Run();
    while(!Something.Finished){
      yield return null;
    }
    yield return 3535;
}

問題点

  • 型安全じゃない(generic の IEnumerator にしてもいいけれど、そうすると、 yield return [Coroutine Object] ができなくなるので、中で StartCoroutine できない)

いまのプロジェクトでは、間にラッパーを挟んで3の方法を使っています。なぜかというとコードがシーケンシャルになるからです。また、C# が採用している async/await に比較的近い見た目のコードになるからというのもあります。

おまけ

自前で IEnumerator を回した時に Unity の WaitForSeconds みたいな Coroutine を返すものを待ってくれないっていうことに躓くことがあります。

たとえば、こういうの

IEnumerator Hoge(){
    var coroutine = Wait();
    while(coroutine.MoveNext())
      yield return null;
    print("hoge");
}

IEnumerator Wait(){
    yield return new WaitForSeconds(1.0f);
}

Unity の Coroutine を待つためには、Coroutine オブジェクトを Current で返してあげないといけないのです。なので、

IEnumerator Hoge(){
    var coroutine = Wait();
    while(coroutine.MoveNext())
      yield return coroutine.Current; // null じゃなくて Coroutine を返す
    print("hoge");
}

IEnumerator Wait(){
    yield return new WaitForSeconds(1.0f);
}

にすれば動きます。