時間を扱う処理:コルーチン・Invoke・一時停止【Unityスタートガイド6-11】

「3秒後に敵を出したい」「1秒ごとに数字を減らしたい」「ポーズボタンでゲームを止めたい」。ゲームを作っていると、時間を扱う処理がたくさん出てきます。今回は、Updateでのタイマー、Invoke、コルーチン、そしてTime.timeScaleを使った一時停止をまとめてご紹介します!

目次

この記事で学べること

  • Time.deltaTimeを足していくタイマーの作り方
  • Invokeで「〇秒後に」メソッドを呼ぶ方法
  • コルーチンで「待ってから続きをする」処理の書き方
  • Time.timeScaleでゲームを一時停止する方法と、止まらない時間の扱い方

前提知識

時間を扱う3つの方法

方法向いている場面
Updateでタイマー残り時間の表示など、毎フレーム時間を見たいとき
Invoke「〇秒後に1回だけ」メソッドを呼びたいとき
コルーチン「待つ → 何かする → また待つ」のように、順番に進めたいとき

どれを使っても出来ることは似ていますが、書きやすさが違います。順番に見ていきましょう。

Updateでタイマーを作る

6-9のTimedBlockでやったように、Time.deltaTimeを足していくと経過時間が分かります。

using UnityEngine;

public class TimeCountdownTimer : MonoBehaviour
{
    [SerializeField] private float limitTime = 10f;

    private float remainingTime;
    private bool isFinished = false;

    void Start()
    {
        remainingTime = limitTime;
    }

    void Update()
    {
        if (isFinished)
        {
            return;
        }

        remainingTime -= Time.deltaTime;

        if (remainingTime <= 0f)
        {
            remainingTime = 0f;
            isFinished = true;
            Debug.Log("タイムアップ!");
        }
    }
}
  • 残り時間を毎フレーム減らしていく
  • 0になったらboolで「終わった」を覚えて、2回目以降は何もしない
  • 残り時間を画面に表示したい場合は、9-4の記事のTimerDisplayと組み合わせます

Invoke:〇秒後にメソッドを呼ぶ

using UnityEngine;

public class TimeInvokeExample : MonoBehaviour
{
    void Start()
    {
        // 2秒後にSpawnMessageを1回呼ぶ
        Invoke(nameof(SpawnMessage), 2f);

        // 1秒後から、0.5秒ごとにTickを呼び続ける
        InvokeRepeating(nameof(Tick), 1f, 0.5f);
    }

    void SpawnMessage()
    {
        Debug.Log("2秒たちました!");
    }

    void Tick()
    {
        Debug.Log("チクタク");
    }

    void OnDisable()
    {
        // 予約していたInvokeを全部取り消す
        CancelInvoke();
    }
}
  • Invoke(メソッド名, 秒数):その秒数のあとに1回だけ呼ぶ
  • InvokeRepeating(メソッド名, 最初の待ち時間, 間隔):くり返し呼ぶ
  • CancelInvoke():予約を取り消す(CancelInvoke(nameof(Tick))のように1つだけ取り消すことも出来ます)
  • メソッド名はnameof(メソッド名)で書きましょう。Invoke("SpawnMessage", 2f)のように文字列で書くと、つづりを間違えてもエラーにならず、何も起きないまま気づけません。nameofならつづりが違うとVS Codeで赤い波線が出ます
  • 引数のあるメソッドはInvokeでは呼べません

コルーチン:待ってから続きをする

「3、2、1、スタート!」のように、待つ → 何かする → また待つを順番に書きたいときは、コルーチンが便利です。

using System.Collections;
using UnityEngine;

public class TimeCoroutineCountdown : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(CountdownRoutine());
    }

    IEnumerator CountdownRoutine()
    {
        for (int i = 3; i > 0; i--)
        {
            Debug.Log(i);
            yield return new WaitForSeconds(1f);   // 1秒待つ
        }

        Debug.Log("スタート!");
    }
}

コルーチンの書き方

  • using System.Collections;を一番上に書く(IEnumeratorを使うため)
  • メソッドの型をIEnumeratorにする
  • yield return new WaitForSeconds(秒数);の行で、そこで待ってから続きを実行する
  • 呼ぶときはStartCoroutine(メソッド名());(普通のメソッドのようにCountdownRoutine();と呼ぶだけでは動きません)

よく使う待ち方

書き方待つもの
yield return new WaitForSeconds(1f);1秒(ゲームの時間)
yield return new WaitForSecondsRealtime(1f);1秒(一時停止中でも進む時間)
yield return null;次のフレームまで
yield return new WaitUntil(() => 条件);条件がtrueになるまで

コルーチンを止める

private Coroutine countdown;

void Start()
{
    countdown = StartCoroutine(CountdownRoutine());
}

void StopCountdown()
{
    if (countdown != null)
    {
        StopCoroutine(countdown);
        countdown = null;
    }
}
  • StartCoroutineの結果を変数に入れておくと、あとでStopCoroutineで止められます
  • オブジェクトが非表示になったり消えたりすると、そのオブジェクトで動いていたコルーチンも止まります

8-3の記事のDelayedSceneLoaderも、「少し待ってからシーンを切り替える」ためにコルーチンを使っていました。仕組みはこの記事と同じです。

スライム

コルーチンを書いたのに、1回目の数字しか出てこない・・・

フェアリー

StartCoroutineで呼んでるかな?それと、再生中にオブジェクトを非表示にするとコルーチンも止まっちゃうから、そこも確認してみてね

Time.timeScaleで一時停止する

Time.timeScaleは、ゲームの時間の進む速さです。

  • Time.timeScale = 1f;:通常
  • Time.timeScale = 0f;:止まる(一時停止)
  • Time.timeScale = 0.5f;:半分の速さ(スローモーション)
using UnityEngine;

public class TimePauseSwitch : MonoBehaviour
{
    private InputSystem_Actions inputActions;
    private bool isPaused = false;

    void Awake()
    {
        inputActions = new InputSystem_Actions();
    }

    void OnEnable()
    {
        inputActions.Enable();
    }

    void OnDisable()
    {
        inputActions.Disable();
        // このスクリプトが無効になるときは、止めたままにしない
        Time.timeScale = 1f;
    }

    void Update()
    {
        // UIのCancel(キーボードのEscキーなど)でポーズの切り替え
        if (inputActions.UI.Cancel.WasPressedThisFrame())
        {
            isPaused = !isPaused;
            Time.timeScale = isPaused ? 0f : 1f;
            Debug.Log(isPaused ? "一時停止" : "再開");
        }
    }
}

Time.timeScale = 0f;にすると、次のものが止まります。

  • Time.deltaTimeが0になるので、それを使った移動やタイマー
  • 物理演算(Rigidbodyの動き)
  • WaitForSecondsで待っているコルーチン
  • Invoke

一方で、Update自体は呼ばれ続けます。だから上のスクリプトのように、止まっている間もキー入力で再開できます。

一時停止中も動かしたいもの

ポーズメニューのアニメーションなど、止まっていてほしくないものは、止まらない時間を使います。

止まる時間止まらない時間
Time.deltaTimeTime.unscaledDeltaTime
WaitForSecondsWaitForSecondsRealtime

よくあるエラーと対処法

エラー:The type or namespace name ‘IEnumerator’ could not be found

  • 原因:using System.Collections;を書いていない
  • 対処法:スクリプトの一番上にusing System.Collections;を足す

エラー:コルーチンが動かない(1行目で止まる、何も起きない)

  • 原因:StartCoroutine(...)ではなく、普通のメソッドのように呼んでいる。またはオブジェクトが非表示になっている
  • 対処法:StartCoroutine(メソッド名());で呼ぶ。オブジェクトが有効になっているか確認する

エラー:Trying to Invoke method: ○○ couldn’t be called.

  • 原因:Invokeに渡したメソッド名のつづりが違う(文字列で書いている)
  • 対処法:Invoke(nameof(メソッド名), 秒数)で書く

エラー:一時停止から戻れない・次のシーンが止まったまま

  • 原因:Time.timeScale = 0f;のまま、シーンを切り替えたり、再開の処理を通らなかった
  • 対処法:シーンを切り替える前や、ポーズを解除するときにTime.timeScale = 1f;に戻す。timeScaleはシーンをまたいでもそのまま残ります

まとめ

  • Updateでタイマー:Time.deltaTimeを足す・引く
  • Invoke:〇秒後に1回(nameofで書く)。くり返しはInvokeRepeating
  • コルーチン:IEnumerator+yield return new WaitForSeconds()+StartCoroutine
  • Time.timeScale:0で一時停止。止まらない時間はunscaledDeltaTimeとWaitForSecondsRealtime

次のステップ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次