Developer

【初心者Unity】Time.deltaTimeの基本的な使い方
2021.09.29
Lv1

【初心者Unity】Time.deltaTimeの基本的な使い方

1.はじめに

本記事では、Unityで時間を扱うための最も入門的な機能であるTime.deltaTimeについて解説します。
Time.deltaTimeを使用することで、以下のことが実現できます。

  • カウントアップするタイマーの作成(時間計測等)
  • カウントダウンするタイマーの作成(時間制限等)
  • 秒単位で処理を行う etc…

解説には以下の構成を使用します。
自身で手を動かして確認したい方はこちらのファイルをインポートしてください。
【初心者Unity】unitypackageのインポートとエクスポート方法

Timerオブジェクトには以下のスクリプトがアタッチされています。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TimerScript : MonoBehaviour
{
    public static bool beingMeasured; // 計測中であることを表す変数

    void Start()
    {
        // 初期値は「計測中ではない」状態
        beingMeasured = false;
    }

    void Update()
    {
        if (!beingMeasured)
        {
            return;
        }
        ///// 「計測中ではない」場合、以降の処理は実行しない /////
        
    }
}

また、Buttonオブジェクトには以下のスクリプトがアタッチされており、OnClickメソッドがボタン押下時のアクションとして定義されています。
【初心者Unity】uGUI(Button)の使い方を詳しく解説

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ButtonScript : MonoBehaviour
{
    private Text buttonText;

    void Start()
    {
        // 自身の子オブジェクトのTextコンポーネントを取得
        buttonText = GetComponentInChildren<Text>();
    }
    // ボタン押下時に実行されるメソッド
    public void OnClick()
    {
        // TimerScriptのbeingMeasuredの値を反転させる
        TimerScript.beingMeasured = !TimerScript.beingMeasured;
        // ボタンの文言を切り替える
        buttonText.text = TimerScript.beingMeasured ? "STOP" : "START";
    }
}

2.Time.deltaTimeの仕組み

Time.deltaTimeは前回のフレームからの経過時間(秒)を表す変数です。
ここで言うフレームとは、Update関数内の処理 + 描画処理(レンダリング)をひとまとめにした単位だと考えて下さい。
基本的には約0.02秒ほどになりますが、PCの性能や実行している処理の重さによってこの時間は前後します。

0.02秒って何の数字?
1秒間に処理されるフレームの数をフレームレート(FPS:Frames Per Second)と言います。
Unityのフレームレートはデフォルトでモニタのリフレッシュレートに同期するよう設定がされています。

一般的なモニターのリフレッシュレートは約60Hzですので、Unityのフレームレートは60FPSになります。
すなわち、1÷60=0.016666…秒(≒0.02秒)がフレーム間の時間になります。
ただし、この値はあくまでも目標値ですので、0.02秒以内に処理が終わらなかった場合はオーバーしてしまうこともあります。
フレームレートを明示的に設定したい場合は、上記画像の項目をDon’t Syncに変更した上で、
[csharp] Application.targetFrameRate = 60; // 60FPSに設定する
[/csharp] のようにスクリプトで指定することができます。

※余談ですが、映画は約24FPSを想定して作成されています。
 FPSを大きくすると非日常性感が薄れるんだそうです。

図を見てわかるように、実質的にはUpdate関数が実行される間隔に等しくなります。

ここで注目して欲しいのは、Time.deltaTimeのです。
Time.deltaTimeによって取得される時間は間断なく計測されるため、その総和は計測の始点から終点までの経過時間になります。
以降では、この性質を利用してタイマーの実装を行っていきます。

3.カウントアップタイマーの作成

はじめに、最も単純な機能としてカウントアップするタイマーの作成を行います。
ゲームでの使用例としては、レースゲーム等が挙げられるでしょうか。
上記で説明した通り、Time.deltaTimeの和は計測開始時点からの経過時間を表しますので、それをTextで表示することで実装できます。
【初心者Unity】uGUI(Text)の使い方を詳しく解説

TimerScriptを以下のように記述します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TimerScript : MonoBehaviour
{
    public static bool beingMeasured; // 計測中であることを表す変数
    private float time; // 経過時間を格納する変数
    private Text timerText;

    void Start()
    {
        beingMeasured = false; // 初期値は「計測中ではない」状態
        time = 0.0f;
        timerText = GetComponent<Text>();
        timerText.text = "0.00";
    }

    void Update()
    {
        if (!beingMeasured)
        {
            return;
        }
        ///// 「計測中ではない」場合、以降の処理は実行しない /////
        time += Time.deltaTime;
        timerText.text = time.ToString("0.00");
    }
}

Time.deltaTimeで取得できる値はfloat型ですので、経過時間を格納するためのfloat型の変数を用意してあげる必要があります。
今回は、timeという変数がそれに当たります。

28行目のToString(“0.00”)はfloat型をstring型に変換するメソッドです。
引数は変換後のフォーマットを意味しており、上記の書き方は「(四捨五入をし、)小数第2位まで表示する」ことを表しています。

以上で設定は完了です。ゲームを実行してみましょう。

4.カウントダウンタイマーの作成

次に、カウントダウンするタイマーの作成を行います。
こちらは制限時間を設けるような仕組みで汎用的に使用できるのではないでしょうか。
カウントアップタイマーの応用で、制限時間から経過時間を引いてあげることで実装できます。

TimerScriptの記述を以下のように変更します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TimerScript : MonoBehaviour
{
    public static bool beingMeasured; // 計測中であることを表す変数
    private Text timerText; // 制限時間を格納する変数
    private float limit;

    void Start()
    {
        beingMeasured = false; // 初期値は「計測中ではない」状態
        limit = 5.0f;
        timerText = GetComponent<Text>();
        timerText.text = "5.00";
    }

    void Update()
    {
        if (!beingMeasured)
        {
            return;
        }
        ///// 「計測中ではない」場合、以降の処理は実行しない /////
        limit -= Time.deltaTime;
        timerText.text = limit.ToString("0.00");
    }
}

limitという変数が制限時間かつ表示される時間を表しています。
27行目でlimitから経過時間を引いており、28行目でその値を表示しています。

以上の設定でカウントダウンタイマーは作成できましたが、このままだと制限時間を終えた後もマイナスの時間がカウントされてしまいます。
実際の用途としては、カウントが0になったタイミングで何らかの処理を行うことになるかと思いますので、そこまで実装してしまいましょう。

スクリプトに以下の記述を追記します。

void Update()
{
    if (!beingMeasured)
    {
        return;
    }
    ///// 「計測中ではない」場合、以降の処理は実行しない /////
    limit -= Time.deltaTime;
    timerText.text = limit.ToString("0.00");

    // ↓追記
    if(limit < 0)
    {
        timerText.text = "タイムアップ";
        beingMeasured = false;
    }
}

limitが0を下回ったら、タイマーの表示を「タイムアップ」に切り替える、という内容になります。

ここで、if文の条件式に等価演算子(==)ではなく比較演算子(<)を使用していることに注意してください。
limitの値は離散的に変化するため、等価演算子では上手く機能しない恐れがあります。
(例えば、limitの値が0.04→0.02→0.01→-0.01→…と変化するようなケースです。)

実際にゲームを再生すると、以下のような動作になります。

5.秒単位で処理を行う

Time.deltaTimeで取得できる経過時間は様々な物理計算に応用することが可能です。
例えば、移動距離の計算では、距離=速さ×時間 という式で表される通り、「速さ」と「時間」の2つの要素が必要になります。
この内の「時間」に対応するのがTime.deltaTimeで取得できる経過時間になります。

ここで、「0.1秒間で1m進みたい」という要件を満たすプログラムを考えてみましょう。
もしかすると、以下のように考えるかも知れません。

“Update関数は0.02秒間隔で呼び出されるから、Update関数が呼び出されるたびに0.2mずつ移動しよう”

この考えは正しいでしょうか?
結論から言うと、この実装はフレームレートによって失敗する可能性があります。

一般的に、Update関数が呼び出される間隔は一定ではありません。
上図のように0.02秒-0.03秒-0.03秒-0.02秒という間隔で実行された場合、4回の実行で0.1秒が経過してしまいます。

この問題を解決するためにはTime.deltaTimeを使用する必要があります。

上図の通り、一度のUpdate関数で移動する距離を 10(m/s) × Time.deltaTime(s) とします。
この時、Time.deltaTimeはフレームレートに応じた重み付けとして機能します。
すなわち、間隔が長い場合はその分移動距離が長くなり、逆に間隔が短い場合は移動距離も短くなります。

このようにTime.deltaTimeを計算式中に組み込むことで、フレームレートに依らない時間単位の移動が実現できます。
これによって、スペックの異なるPC間であっても、見た目上は同様に動作するゲームを作成することができるのです。

6.おわりに

Time.deltaTimeの使い方について簡単に解説しました。
今回の記事で解説した内容は頻出ですので、是非ご自身で書けるように練習してみて下さい。
また、関連記事として以下を紹介します。併せてご確認ください。

【初心者Unity】Time.timeの基本的な使い方
【初心者Unity】Time.deltaTimeとTime.timeの使い分け
【初心者Unity】Time.timeScaleの基本的な使い方

 

連載目次リンク

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

Unity実践編 - 目次リンク

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