ゲームオブジェクトの移動とかにTime.deltaTimeを乗算する意味がよく分からない![Unity]

Unity

表題の通りなのですが、例えばオブジェクトの移動時に
transform.Translate(1.0f * Time.deltaTime , 0 , 0);
みたいに移動にTime.deltaTimeを掛けていることがサンプルのアセットなどを見ると結構ありますよね。

最初は「あー、そういうもんなんだろーなー」ぐらいの
いわゆる「おまじない」として記憶していたのですが
本当によく見るんですよ、これ。

見れば見る程なんでこれがいるの?
っていうのがどうしても気になってきました。

なんとなくTime.deltaTimeの意味は理解していたつもりなのですが
正直よく分かっていないので色々調べてみました。
調査結果をまとめたものをこちらに記載していきます。

そもそもTime.deltaTimeとはなんなのか?

Time.deltaTimeとは前フレームの表示開始から現在のフレーム表示開始までにかかった時間(秒)を返してくれるそうです。

フレームっていうのは「一枚の画像」っていう意味で捉えればとりあえずはいいかと思います。

ではこのTime.deltaTimeが必要な要因は何なのかについてです。

それはずばりゲームする環境が人によって違うから、ということです。
例えば2010年頃に発売したスマホと2020年頃に発売したスマホのスペックを比べると、
当然2020年発売のスマホの方が性能が良くなっているはずですよね。

この両者で同じゲームをした場合、性能の違いによって
1フレームを画面に表示する時間っていうのは差があるはずです。

具体的に考えてみましょう。以下の2つのスマホがあったとします。

スマホA:1秒間に10フレームを画面に表示できる性能
スマホB:1秒間に20フレームを画面に表示できる性能

この場合、スマホBはスマホAに比べて1秒間に2倍フレームを表示できます。
そうするとどうなるかというとスマホBはスマホAの2倍速でゲームが進行してしまうことになります。
これは避けたいですよね。このゲーム機によるフレーム処理の差を吸収してくれるのが
Time.deltaTimeというわけなんですね。

Image by FelixMittermeier from Pixabay

ここまででTime.deltaTimeの概念的な部分が分かったかと思います。
以下では実際の処理を計算している部分について詳細をみていきましょう。
前提として
transform.Translate(1.0f * Time.deltaTime , 0 , 0);
の処理はUpdate関数内に記述されているとします。

まずTime.deltaTime以外で
登場する関数は以下2つです。
・Update
・Time.TimeScale

この2つとTime.deltaTimeが混ぜ合わさっているから
なんか分かりにくいんだと思います。
それぞれゲームオブジェクトの移動にどう関わっているのか?
みていきましょう。

UpdateとTime.deltaTimeの関係について

まずUpdate関数についてです。
ゲームオブジェクトを移動させる時は大体Update関数内に記載するかと思います。
Update関数は毎フレーム呼び出される処理だそうです。

Time.deltaTimeは上記で述べた通り1フレームを処理する時間です。

transform.Translate(1.0f * Time.deltaTime , 0 , 0);
が何をしているかというとx座標を1移動しようとしています。
そしてそこに1フレームを処理する時間であるTime.deltaTimeを掛けています。

まだよくわかりませんよね。
もっと具体的に見ていきましょう。
上記と同じスマホを使うとして

スマホA:1秒間に10フレームを画面に表示できる性能
スマホB:1秒間に20フレームを画面に表示できる性能

だとすると
スマホAでのTime.deltaTime = 1秒 ÷ 10(1秒に処理するフレーム量) = 0.1秒
スマホBでのTime.deltaTime = 1秒 ÷ 20(1秒に処理するフレーム量) = 0.05秒
になります。
(Time.deltaTime = 1フレームを処理する時間なので
1秒間に何フレームを処理するかで求めることができます。)

Update関数内に
transform.Translate(1.0f * Time.deltaTime , 0 , 0);
の処理を追加すると、この移動の処理が毎フレーム呼び出されます。

つまり
スマホAでの1フレームの移動量 = 1.0(今回のx座標の移動量) * 0.1(1フレームを処理する時間) = 0.1
スマホBでの1フレームの移動量 = 1.0(今回のx座標の移動量) * 0.05(1フレームを処理する時間) = 0.05
になります。

では逆に1秒でどのくらい進むのかというと
スマホA : 0.1(1フレームの移動量) * 10(1秒に処理するフレーム量) = 1
スマホB : 0.05(1フレームの移動量) * 20(1秒に処理するフレーム量) = 1
とスマホの性能に関わらず同じ結果になります。

つまり1秒間にどんなにフレームを処理しても同じ距離しか進まなくなるんです!
なるほど、これは必須で実装しないといけなそうですね…。

Photo by pine watt on Unsplash

Time.TimeScaleとTime.deltaTimeの関係について

ここまで理解できれば大体OKだと思いますので次は応用版です。

Time.TimeScaleについてですね。
これはゲーム世界での時間間隔を変更できるものです。
初期設定でTime.TimeScale = 1になっています。つまり現実世界で1秒進むとゲーム世界も1秒進むみます。
例えばTime.TimeScale = 2にするとゲーム世界は2倍速になります。つまり現実世界で1秒進むとゲーム世界は2秒進むんです。
Time.TimeScale = 0.5にするとスローモーションになります。つまり現実世界で1秒進んでもゲーム世界は0.5秒しか進まないんです。
Time.TimeScale = 0にすると完全に時間が停止します。つまり現実世界で何秒過ぎようともゲーム世界は時間が停止したままです。
とはいえ全ての処理が停止してしまったらパソコンのフリーズ
と変わらなくなるので、壊れたのと一緒になってしまいます。
このためUpdate関数はTime.TimeScale = 0でも動作するとのことでした。
ポーズ画面でボタンを操作したりする時は処理動いて欲しいですもんね。

ただ、ここで1つの疑問が沸きました。
Time.TimeScale = 0にしたとき
Update関数内でTime.deltaTimeはどんな値を返すのか?です。

Time.TimeScaleの値とTime.deltaTimeは関連性があるはずです。
なぜならTime.TimeScale = 2にした場合、1秒間でのフレームの表示も2倍に増えるはずだからです。
例えばTime.TimeScale = 1の場合Time.deltaTime = 1だったとすると
Time.TimeScale = 2に変更したらTime.deltaTime = 0.5になるはずです。
逆にTime.TimeScale = 0.5に変更したらTime.deltaTime = 2になるはずです。
つまり反比例の関係にあるはずなんです。
そうするとTime.TimeScale = 0の場合Time.deltaTime = 無限大になる…?

頭が爆発しそうなので、どうなるのか実際にやってみましょう。
まずは実行結果を確認してみてください。

上がゲーム画面、下がConsoleの表示です。

Time Stopというボタンを押した途端、Time.deltaTimeの値が0になりました!
つまりTime.timeScale = 0にしてもTime.deltaTime = 0になるんですね。
移動にTime.deltaTimeを掛けるとTime.timeScale = 0としたときも移動しなくなるということになります。

どんな実装をしたのかというのを以下に記載していきます。
Unityでプロジェクトを新規作成しTimeStop.csという名前のC#Scriptを新規作成し、中身は以下のようにしました。

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

public class TimeStop : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Time.timeScale = 1;
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("Time.deltaTime = " + Time.deltaTime);
    }

    public void timeStop()
    {
        Time.timeScale = 0;
    }
}

わざわざ書く必要もないのですが明示的にStart()でTime.timeScale = 1;と記述しています。

そしてUpdateでTime.deltaTimeの値を表示します。

timeStop関数が実行されると
Time.timeScale = 0;
に設定されるという仕組みです。

Hierarchyウィンド→右クリック→Create Emptyで空のゲームオブジェクトを作成しTimeStopのC#Scriptをアタッチします。

更にUIからButtonを新規作成し、On Click()にさきほど作成した空のゲームオブジェクトのtimeStopが実行されるようにします。

これでゲームが始まりボタンがクリックされるとtimeScaleが0に変更されます。

まとめ!

いかがでしたでしょうか。

updateとdeltaTimeの関係
timeScaleとdeltaTimeの関係

私みたいに意外とごっちゃになっている方いらっしゃるんじゃないでしょうか。

こちらの記事が頭の整理になったら幸いです。

Image by Free-Photos from Pixabay
タイトルとURLをコピーしました