はかせのラボ

私の頭の中を書いていく雑記ブログです

Unity コルーチンでコルーチンを起動して起動したコルーチンでまたコルーチンを起動してetc...

あいさつ

どうも、はかせです。
今回はちょっと普段とは違う開発環境で開発することになって
async/awaitが使えないC#で開発することになりました_( _´ω`)_ツライム
ということで普段はasync/awaitでやっていたループ構築を
コルーチンで行ったのでその話です。

コルーチンでゲームループを組みたい理由

普通にUnityでゲームループを組もうとすると
MonoBehaviourを継承したクラスの
Awake、Start、Update辺りを使って組んでいくと思います。

ただこいつらを使っていくと否が応でもUnityにタイミングを縛られた
ループを構築することになります。
つまり毎フレーム何かしてその連続で構築していくわけです。

大体がそれで問題はないのですがたまにめんどくさかったりします。
例えば
・キャラのAI
・重いデータの処理

みたいなフレームベースじゃなくて
時間や結果ベースでループを回してく場合です。

こういう時に私はasync Taskの非同期処理の組み合わせで
処理単位ごとにループを回していきます
ただasync/awaitはC#6.0つまり
Unity2017.x以降のバージョンでなければ使えません
あとasyncを付けると呼び出し側にもasyncを付けることを強要される上
awaitを中に書かないとwarningが鬱陶しかったりします。
この辺の解決というかごまかし方は過去に記事に上げましたので
そちらをどうぞ
hakase0274.hatenablog.com

コルーチンだとその辺の鬱陶しい部分が
StartCoroutine辺りに押し込まれてるので使う側は楽です。
結果の取得がめんどくさいですがあくまでこの結果や時間ベースのループは
その完了を待てれば事足りるので結果を取得する必要がありません。

なんでもしかしたらこっちの方が楽かもしれません。
まぁ私は色々ごにょることが多いので
結果を楽に取れるasync/awaitの方が好きです。はい。

実装

長々と話してきましたが
とりあえずコードです。

public class CoroutineLoop : MonoBehaviour
{
    private enum State
    {
        One,
        Two,
        Three,
        Four,
        End
    }

    private State mState;
    // Use this for initialization
    void Start()
    {
        mState = State.One;
        StartCoroutine(GameLoopCoroutine());
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            mState = State.One;
        }
        if (Input.GetKeyDown(KeyCode.B))
        {
            mState = State.Two;
        }
        if (Input.GetKeyDown(KeyCode.C))
        {
            mState = State.Three;
        }
        if (Input.GetKeyDown(KeyCode.D))
        {
            mState = State.Four;
        }
        if (Input.GetKeyDown(KeyCode.E))
        {
            mState = State.End;
        }
    }

    private IEnumerator GameLoopCoroutine()
    {
        while (true)
        {
            switch (mState)
            {
                case State.One:
                    yield return StartCoroutine(StateOneCoroutine());
                    break;
                case State.Two:
                    yield return StartCoroutine(StateTwoCoroutine());
                    break;
                case State.Three:
                    yield return StartCoroutine(StateThreeCoroutine());
                    break;
                case State.Four:
                    yield return StartCoroutine(StateFourCoroutine());
                    break;
                case State.End:
                    print("GameEnd");
                    yield break;
            }
        }
    }

    private IEnumerator StateOneCoroutine()
    {
        print("One");
        yield return new WaitForSeconds(1);
    }

    private IEnumerator StateTwoCoroutine()
    {
        print("Two");
        yield return new WaitForSeconds(1);
    }

    private IEnumerator StateThreeCoroutine()
    {
        print("Three");
        yield return new WaitForSeconds(1);
    }

    private IEnumerator StateFourCoroutine()
    {
        print("Four");
        yield return new WaitForSeconds(1);
    }
}

長いですが脳死で組めましたw
結局は
・ループ管理コルーチンをStart辺りで起動
・ループ管理コルーチンで各種コルーチンを実行し処理を待ち合わせ
・Updateとかはユーザー入力などのイベント管理に全投資

(めっちゃ適当に組んでるやん・・・w)

ちょっと準備がだるいですが
こうするとあとは処理単位でコルーチンを組んで
管理コルーチンで良きに計らうだけで
なんちゃってステートマシンや
順序立てて処理を行うことが出来ます。

ただ各コルーチンの途中で何らかの条件が揃ったら
別の処理へ即時切り替えたいみたいな要望も
AIとか作ってたら発生すると思います。

そういう時にUpdateを使います。
コルーチンの動きとUpdateの動きはなんちゃって非同期で
お互いに干渉しないのでUpdateで入力だとかイベントを検知して、
メンバにフラグかなんかを設定しコルーチンでフラグを見て処理を変える
みたいにすればこれまたなんちゃってイベントシステムの完成です。
(なんちゃってが多い)

あとがき

今回はコルーチンを使ってゲームループの構築でした。
やってることは色んなとこでいろんな形が実装されている
タスクシステムと同じですね。
それをUnityのコルーチンとUpdateの合わせ技でお手軽に実装するやり方でした。

Unityである程度C#使える方であれば
難なく実装できると思います。
(めんどくさいですがw)

もしAIのような結果や時間ベースでループを回したいときは
実装方法の候補として検討してみてはいかがでしょうか?

それでは今回はこの辺でノシ