ステート管理

2019年10月9日

実行フェーズを管理する方法はいろいろあります。
実行フェーズとは、「攻撃中である」「防御中である」「自分のターン」など現在の状態のこと。今、どの状態にあるのかを管理することで次の状態に遷移することができるようになります。

コルーチンを使う

管理用ループ

ゲームループメソッドでは、各ステートごとのメソッド呼び出しの完了を待つことでステートを管理します。
順番に確認できるので、ステートの順がわかりやすいです。

// ゲームが終了したかをチェックするためのフラグ
public bool IsGameEnd { get; private set; } = true;

// これはスタートから呼び出され、各フェーズを次々と実行していきます。
IEnumerator GameLoop()
{
    // まず、「RoundStarting」コルーチンを実行しますが、終了するまで戻りません。
    yield return StartCoroutine(RoundStarting());
    // Unity5.3以前 yield return StartCoroutine(RoundStarting());

    // RoundStarting」コルーチンが終了したら、「RoundPlaying」コルーチンを実行しますが、終了するまで戻りません。
    yield return StartCoroutine(RoundPlaying());
    // ここで実行が戻ってきたら、「RoundEnding」コルーチンを実行しますが、ここでも終了するまで戻りません。
    yield return StartCoroutine(RoundEnding());
    // このコードは「RoundEnding」が終了するまで実行されません。 この時点で、ゲームの勝者が見つかったかどうかをチェックします。
    if (IsGameEnd)
    {
        // ゲームの勝者がいる場合は、レベルを再起動します。
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
    else
    {
        // まだ勝者がいない場合は、このコルーチンを再起動してループを続けます。
        // このコルーチンは降伏しないことに注意してください。 これは、現在のバージョンのGameLoopが終了することを意味する。
        StartCoroutine(GameLoop());
    }
}

管理用ループの呼び出し

コルーチンを呼び出すためのスタートメソッド

void Start()
{
    Debug.Log("Start");
    StartCoroutine(GameLoop());
}

まとめると次のようになります。

全体のコード

GameManager.cs

チュートリアルのサンプルですが、コルーチンで別のコルーチン完了待ちをする仕組みです。
使い方のパターンを覚えると応用が効きます

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace EasySample
{
    public class GameManager : MonoBehaviour
    {
        void Start()
        {
            Debug.Log("Start");
            StartCoroutine(GameLoop());
        }

        // ゲームが終了したかをチェックするためのフラグ
        public bool IsGameEnd { get; private set; } = true;

        // これはスタートから呼び出され、各フェーズを次々と実行していきます。
        IEnumerator GameLoop()
        {
            // まず、「RoundStarting」コルーチンを実行しますが、終了するまで戻りません。
            yield return RoundStarting();

            // RoundStarting」コルーチンが終了したら、「RoundPlaying」コルーチンを実行しますが、終了するまで戻りません。
            yield return RoundPlaying();

            // ここで実行が戻ってきたら、「RoundEnding」コルーチンを実行しますが、ここでも終了するまで戻りません。
            yield return RoundEnding();

            // このコードは「RoundEnding」が終了するまで実行されません。 この時点で、ゲームの勝者が見つかったかどうかをチェックします。
            if (IsGameEnd)
            {
                // ゲームの勝者がいる場合は、レベルを再起動します。
                SceneManager.LoadScene(SceneManager.GetActiveScene().name);
            }
            else
            {
                // まだ勝者がいない場合は、このコルーチンを再起動してループを続けます。
                // このコルーチンは降伏しないことに注意してください。 これは、現在のバージョンのGameLoopが終了することを意味する。
                StartCoroutine(GameLoop());
            }
        }

        IEnumerator RoundStarting()
        {
            Debug.Log("ゲーム開始");
            yield return new WaitForSeconds(2.0f);
        }

        IEnumerator RoundPlaying()
        {
            Debug.Log("ゲーム最中");

            // Playerが残っていない
            while (!PlayerLeft())
            {
                // 次のフレームへ
                yield return null;
            }
        }

        IEnumerator RoundEnding()
        {
            Debug.Log("ゲーム終了");
            yield return new WaitForSeconds(2.0f);
        }

        int playerCount = 0;

        bool PlayerLeft()
        {
            return playerCount == 0;
        }
    }
}

GameManager.cs(他のyield returnパターンを組み合わせたサンプル)

WaitWhile,WaitUntileを使ったパターン

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace EasySample
{
    public class GameManager : MonoBehaviour
    {
        void Start()
        {
            Debug.Log("Start");
            StartCoroutine(GameLoop());
        }

        // ゲームが終了したかをチェックするためのフラグ
        public bool IsGameEnd { get; private set; } = true;
        public int PlayerCount { get; private set; } = 0;

        // これはスタートから呼び出され、各フェーズを次々と実行していきます。
        IEnumerator GameLoop()
        {
            // 引数にデリゲートを使いブロックの終了を待ちます。
            // trueの間、waitする
            yield return new WaitWhile(() =>
            {
                Debug.Log("ゲーム開始");
                return false;
            });

            // 引数にデリゲートを使いブロックの終了を待ちます。
            // falseの間、waitする
            yield return new WaitUntil(RoundStarting);

            // ここで実行が戻ってきたら、「RoundEnding」コルーチンを実行しますが、ここでも終了するまで戻りません。
            yield return RoundEnding();

            // このコードは「RoundEnding」が終了するまで実行されません。 この時点で、ゲームの勝者が見つかったかどうかをチェックします。
            if (IsGameEnd)
            {
                // ゲームの勝者がいる場合は、レベルを再起動します。
                SceneManager.LoadScene(SceneManager.GetActiveScene().name);
            }
            else
            {
                // まだ勝者がいない場合は、このコルーチンを再起動してループを続けます。
                StartCoroutine(GameLoop());
            }
        }

        bool RoundStarting()
        {
            Debug.Log("ゲーム最中");
            return PlayerCount == 0;
        }

        IEnumerator RoundEnding()
        {
            Debug.Log("ゲーム終了");
            yield return new WaitForSeconds(2.0f);
        }
    }
}

Switchを使う

(作成中)

C#の基本的な構文で構成します

デザインパターンのStateパターンを使う

(作成中)

高度な管理

アセットを使う方法です。細かな管理が必要になる場合に使います

2019年10月9日Unity

Posted by hidepon