Developer

【初心者Unity】コルーチンの使い方
2021.12.23
Lv1

【初心者Unity】コルーチンの使い方

1.はじめに

ゲームを作成していると、一定時間処理を待ちたい場面があります。

例えば、FPSゲームで銃のリロードをしたとき、少し待たないとリロードが完了しない、なんてことがありますよね。

他の例で言えば、スキルのクールタイムなんかもそうでしょうか。
一度使用したスキルは30秒待たなければ再度使えるようにならない、という制約があったりしますよね。

こうした「一定時間処理を待つ」処理を実現するために、Unityではコルーチンと呼ばれる便利な機能が用意されています。

本記事では、そんなコルーチンの解説をしていきます。
書き方が少しややこしいところはありますが、是非使いこなせるようになりましょう!

2.コルーチンを使う必要ある?

早速コルーチンの使い方について見ていきましょう…と言いたいところですが、
コルーチンの便利さを理解するためには、コルーチンを使わない不便さを体験していただかなくてはなりません。

試しに、最初に挙げた例「銃のリロードをしたとき、少し待たないとリロードが完了しない」をコルーチンを使わない方法で実装してみます。
要件は以下の通りです。練習問題がてら考えてみてください。

<要件>
・左クリックでリロードを開始する
・リロードを開始してから完了するまで2秒かかる
・リロード中はリロードを開始できない

私はこんな感じで書きました。

public class Script : MonoBehaviour
{
    int bullets; // 残弾数
    float reloadTime; // リロードにかかる時間
    
    void Start()
    {
        bullets = 5;
        reloadTime = 0.0f;
    }
    
    void Update()
    {
        // リロード済み
        if (reloadTime <= 0.0f)
        {
            // マウス左クリック
            if (Input.GetMouseButtonDown(0))
            {
                // リロードのアニメーション開始 etc...
                Debug.Log("リロード開始");

                // リロード時間セット
                reloadTime = 2.0f;
            }
        }
        else
        // リロード中
        {
            // リロード時間カウントダウン
            reloadTime -= Time.deltaTime;

            // リロード時間が0秒以下になったら
            if (reloadTime <= 0.0f)
            {
                // 弾を補充
                bullets = 5;
                Debug.Log("リロード完了");
            }
        }
    }
}

if文とTime.deltaTimeを使うことで実現できますね。

一方、同じことをコルーチンを使って書くと次のようになります。

public class Script : MonoBehaviour
{
    int bullets; // 残弾数
    bool reloading; // リロード中であるか否かを表すフラグ
    
    void Start()
    {
        bullets = 5;
        reloading = false;
    }

    void Update()
    {
        // マウス左クリック かつ リロード中ではない
        if (Input.GetMouseButtonDown(0) && !reloading)
        {
            // リロード開始
            StartCoroutine(Reload());
        }
    }

    private IEnumerator Reload()
    {
        // リロードのアニメーション開始 etc...
        Debug.Log("リロード開始");
        reloading = true;

        // 2秒待機
        yield return new WaitForSeconds(2);

        // 弾を補充
        bullets = 5;
        Debug.Log("リロード完了");
        reloading = false;
    }
}

if文のネストがなくなって、読みやすくなりましたね。
何より、Reloadメソッドには、
リロード開始

2秒待機

リロード終了

という一連の流れが、そのまま記述されています。

いかがでしょうか?
コルーチンを使うことは必須ではありません。
ですが、コルーチンを使うことで、多少複雑な処理も簡単に記述できる、何より読みやすいコードが書ける、ということが何となくわかったのではないでしょうか。

3.コルーチンとは

それでは、コルーチンについて説明していきます。

コルーチンは特殊なメソッドのようなものです。
通常メソッドは、内部の処理を上から下に、淀みなく実行していくものですが、
コルーチンでは、処理を一時停止することができます。

コルーチンの書き方
コルーチンは戻り値がIEnumerator型のメソッドとして定義します。
また、yield returnもしくはyield breakどちらかの記述が必要になります。
それ以外は通常のメソッドと変わりません。

private IEnumerator SampleCoroutine()
{
    yield return null;
    yield return null; // 複数記述することができる
}

yield returnに指定できる値には以下のものがあります。

private IEnumerator SampleCoroutine()
{
    yield return null; // 1フレーム待機
    yield return new WaitForSeconds(3); // 3秒間待機
    
    // TheOtherCoroutineを実行、TheOtherCoroutineが終わるまで待機
    yield return StartCoroutine("TheOtherCoroutine");
    
    // コルーチンを終了、省略した場合でも最下行まで実行されればコルーチンは終了する
    yield break;
}

コルーチンの実行
上で先に触れてしまいましたが、コルーチンの実行には、StartCoroutineメソッドを使います。

void Start()
{
    StartCoroutine(SampleCoroutine());
    StartCoroutine(SampleCoroutine()); // 複数記述することができる
    StartCoroutine("SampleCoroutine"); // コルーチン名の文字列を引数に渡すこともできる
}

コルーチンが引数を持つ場合は次のように書きます。

void Start()
{
    StartCoroutine(ArgumentCoroutine(10));
    StartCoroutine("ArgumentCoroutine", 10); // 文字列の場合は第2引数に渡す
    StartCoroutine(ArgumentCoroutine2(10, 20)); // 引数が複数ある場合は文字列での指定はできない
}

private IEnumerator ArgumentCoroutine(int sec)
{
    yield return new WaitForSeconds(sec);
}
private IEnumerator ArgumentCoroutine2(int sec1, int sec2)
{
    yield return new WaitForSeconds(sec1);
    yield return new WaitForSeconds(sec2);
}

コルーチンの停止
StopCoroutineメソッドで実行中のコルーチンを停止することができます。

void Update()
{
    StopCoroutine("SampleCoroutine");
    StopCoroutine(SampleCoroutine()); // これでは停止しないことに注意(別のコルーチンだと認識されるため)
}

StartCoroutineメソッドへの引数の渡し方と合わせる必要があることに注意してください。

void Start()
{
    StartCoroutine(SampleCoroutine());
}

void Update()
{
    StopCoroutine("SampleCoroutine"); // 文字列でStartしていないので停止しない
}

引数を持つコルーチンをStopCoroutineメソッドで停止させたい場合は、当該コルーチンを変数に格納する必要があります。

IEnumerator argumentCoroutine;
void Start()
{
    argumentCoroutine = ArgumentCoroutine(10);
    StartCoroutine(argumentCoroutine);
}

void Update()
{
    StopCoroutine(argumentCoroutine);
    StopCoroutine("SampleCoroutine"); // 文字列でStartしていないのでこちらでは停止しない(一応注意)
}

なお、コルーチンを変数に格納する方法ではStopCoroutineメソッドは一時停止という動作になります。
再度、StartCoroutineメソッドに渡すことで続きから再開することができます。
これは、コルーチンが変数によって参照されているため、破棄されないことによります。

IEnumerator argumentCoroutine;
void Start()
{
    argumentCoroutine = ArgumentCoroutine(10);
    StartCoroutine(argumentCoroutine);
}

void Update()
{
    StopCoroutine(argumentCoroutine);
    StartCoroutine(argumentCoroutine); // 続きから再開される
}

4.練習問題

コルーチンの書き方がわかったところで、練習問題を1題出します。
以下の要件を満たすスクリプトを書いてみてください。

<要件>
・モンスターが倒されてから30秒後にリポップ(再出現)する
解答例
public class MonsterScript : MonoBehaviour
{
    public bool isDead; // 倒されたフラグ
    void Start()
    {
        isDead = false;
    }

    void Update()
    {
        if (isDead) // 他のスクリプトから isDead = true にすることを想定
        {
            // 画面からの表示・当たり判定等を無効にする処理を記述
            StartCoroutine("Repop");
        }
    }

    private IEnumerator Repop()
    {
        yield return new WaitForSeconds(30); // 30秒間待機
        Debug.Log("リポップ");
        // 画面への表示・当たり判定等を有効にする処理を記述
    }
}

6.おわりに

コルーチンについて解説しました。
yield returnなど、慣れない書き方はありますが、やっていることは非常に簡単です。
慣れると、実装は早くなるし、コードの可読性も上がるしでメリットしかありません。
使える場面も多いと思いますので、積極的に導入してみてください!

 

連載目次リンク

「初心者のための」Unityゲーム制作 目次
 

Unity実践編 - 目次リンク

実践Unityゲームプログラミング 連載目次