5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UnityのTime.timeやTime.deltaTime、Time.unscaledTimeなどはリアル時間ではない話

Last updated at Posted at 2021-06-25

Unityの時間に関することは2種類あります

時間としては、リアル(現実)時間とゲーム内時間があります。
1倍のままでマシンの負荷がかからなければ、ゲーム内時間とリアル時間はほぼ同一です。
しかし、3Dモデルなどの読み込みや、そのマシンで別の作業があったりして負荷がかかるとずれる時があります。

ちなみにこの記事は、むしろTime.timeTime.deltaTime を使ったほうが良いという記事です。
ただ、リアルな時間計測では使わないでくださいというものです。

リアルタイム対戦する場合は、マシンごとの負荷に合わせるよりリアル時間に合わせたほうが良いときもあるのでTime.timeTime.deltaTimeを使わないほうが良いときもあります。

リアル(現実)時間

これは文字通り現実の時間です。

Time.realtimeSinceStartup で取得できるものや
C#標準のDateTimeStopwatch はリアル時間を使っています。

これはマシンの性能や、負荷がかかるようなことをしても現実世界の経過時間のままです。

ゲーム内時間

ゲーム内で使われる時間です。
物理挙動などもこの時間をベースに動いています。

簡単に言うと、30分アニメなのに作中では3分しか経ってないみたいなイメージです。

マシンの負荷状況によっては、現実世界からずれていくこともあります。
特に3Dモデルとかバシバシ使うと負荷が高くなります。

タイムアタック機能などを作ったときもマシンのスペックによらず一応公平な結果を出すことができます。
(人が操作できるかどうかはおいておいて)

ゲーム時間は、

Time.timeScale = 2;

のようにして時間を早くして、ゲーム全体を倍速で動かしたり遅くしたり全体を止めることができます。これができるという特徴もあります。

よくあるコード例

例えば、前回から1秒後に攻撃できるようになるみたいなコード例は下のように書いたりします。

if (Time.time - lastAttack >= 1)
{
    attack();
    lastAttack = Time.time;
}

完全に正しいコードです、全く間違いはありません。
ですが、必ずしもリアル時間ではないことに気をつけてください。
リアル時間ではないけど良いんです。ゲーム内時間としてあっていればよいのです。

(負荷が高いときに)時間がずれてておかしいという記事も見かけます。
それもそのはずでTime.time はゲーム時間だからです。
なので、現実の時間を測るときには使用しないようにしましょう。

もし、本当の時間で1秒後をしたいときは上のコードではなく

if (Time.realtimeSinceStartup - lastAttack >= 1)
{
    attack();
    lastAttack = Time.realtimeSinceStartup;
}

などみたいにしましょう、ゲームでこうするのはおすすめはしませんが。

公式ドキュメントでは?

このフレームの開始する時間(Read Only)。ゲーム開始からの時間(秒)です。

とありますが、この記述でせいで誤解が生まれやすくなっていると思われます。
「ゲーム開始からの時間(秒)」はゲーム時間です。リアルの時間ではありません。
ドキュメントが悪い んです。

Time.realtimeSinceStartupの方は

ゲーム開始からのリアルタイム(秒)(Read Only)」

と書いてあります、リアルタイム(秒)が、リアル時間のことなんですね。
他のAPIでも「リアルタイム」と書かれていたら、リアル時間なんだと思います。

検証コード


public class Script : MonoBehaviour
{
    void Update()
    {
        Thread.Sleep(800);
        Debug.Log("Time.time:" + Time.time);
        Debug.Log("Time.deltaTime:" + Time.deltaTime);
        Debug.Log("Time.realtimeSinceStartup:" + Time.realtimeSinceStartup);
    }

    private void FixedUpdate()
    {
        Thread.Sleep(300);
        Debug.Log("Time.fixedTime:" + Time.fixedTime);
    }
}

このコードを適当にアタッチして実行してください。

Thread.Sleepは負荷がかかっている処理の代替です。

さて実際のログはこの様になってなりました。

image.png

image.png

実際では6秒経っていますTime.realtimeSinceStartup はちゃんと6秒経ったのに
Time.time は0.3秒しか経っていません。

ちなみに、Time.unscaledTimeも0.3秒経つことになります。

ゲーム内時間を紐解く

さてこのゲーム内時間はどうなっているのでしょう。

上の処理を見ていくと Time.timeの処理は 0.33333ずつ増えていくのがわかります。

Time.deltaTime も 0.33333となっています。

つまりゲーム内時間は、Time.deltaTimeの値ずつ増えていくというのが想像できます。

さて、0.8秒待ってるのにどこから0.33333の値が来ているのでしょうか。

答えはProject Settingsの「Time」→「Maximum Allowed Timestep」です。

image.png

これは1フレームゲーム内時間の最大を0.33333 とするというもので、
ずっと負荷がかかり続けていると Updateが呼ばれなくなったりキーボードの状態などが取らなくなるので、せめて、0.33333の秒(ゲーム内時間)が経ったときにUpdateなどが呼ばれるようになっているようです。

先程のログで、Update関数が実行されるまでに FixedUpdateがたくさん呼ばれております。

こっちは0.02ずつ増えています。

image.png

こちらはFixed Timestepの0.02です。

実際の動き

これらの動きを理解するためにはまずこの「スクリプトライフサイクルのフローチャート」を理解する必要があります。

Physicsのところにループがあるのがポイントです。

具体的なドキュメントがないので推測にはなりますが、下のように動いているのではないかと思われます。

以後、数値にしたほうがわかりやすいので、 0.02Fixed Timestepのことであり、 0.3333Maximum Allowed Timestepのことです。

FixedUpdate の後にリアル時間に追いつくようにゲーム内時間Time.time += 0.02 され時間が経過するようになっている。

  1. Physicsフェーズの最初でリアル時間の経過が0.02秒未満の場合、ループを抜けUpdateなどが呼ばれる。
    そもそも、FixedUpdateが呼ばれないこともある。

  2. リアル時間の経過が0.02秒以上の場合、physicsのループを行い、FixedUpdateが何度も呼ばれ、なるべくリアル時間に近づく。
    physicsのループが0.33333 秒経っても抜けない場合は、抜けて Updateなどの処理をする。このとき特にリアル時間とゲーム内時間がずれる。

また、リアルな時間とずれていても追いつきそうなら追いついていきますので、負荷が少なくなってもいつまででもずれているというわけではありません。

ちなみに、Time.timeScale=2 などにすると、Maximum Allowed Timestepが2倍になり、
Fixed Timestep自体は変わらず、

2.の部分が「リアル時間の経過が0.01秒以上」などのようになり FixedUpdateの関数が2倍呼ばれるようになるようです。
FixedUpdate1回分の時間経過は変わらない)

このとき
Time.unscaledTime += 0.01 のようになってtimeScaleの逆数が係数としてかけられる。

同じフレームでの値

ちなみに、Time.timeTime.deltaTimeはその瞬間の値ではなく Update の直前に更新される値になりますので、関数内ではどこで呼び出しても同じ値になります。

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

public class GameController : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        Debug.Log("frame " + Time.frameCount);
        Debug.Log("time " + Time.time);
        Debug.Log("real " + Time.realtimeSinceStartup);
        Debug.Log("diff " + (Time.realtimeSinceStartup - Time.time));

        System.Threading.Thread.Sleep(400);
        Debug.Log("sleep");
        Debug.Log("time " + Time.time);
        Debug.Log("real " + Time.realtimeSinceStartup);
        Debug.Log("diff " + (Time.realtimeSinceStartup - Time.time));
    }
}

image.png

Time.timeは同じフレーム内で変わらない、
Time.realtimeSinceStartupは 同じフレーム内でも変わるということを確認しました。

結論

ドキュメントで「ゲーム開始からの時間(秒)」などと書かれているものはゲーム時間のことです。
「リアルタイム」と書かれていたらリアル時間です。

Time.timeがリアル時間ではないと言って、使わないのではなくぜひ使ってください。
ですが、現実の時間ではないことには気をつけてください。

FixedUpdate が一回終わるごとにゲーム内時間がFixed Timestep 分増え、何度も呼ばれることによりリアル時間に追いつこうとする、
FixedUpdate関数が終わるごとに物理シミュレーションされる。

FixedUpdate は固定の時間で呼ばれる」とはあるが、確かにリアル時間に合わせようとはするが、リアル時間で固定されているわけではなくゲーム内時間の 0.02ごとに呼ばれる。
リアル時間からラグは起こるかもしれないが、ゲーム自体は崩壊しないようにしている。

処理が重いことがあると ゲーム内時間経過 != リアル時間経過 になる。 負荷が軽減されるようになると、リアル時間に戻ろうとする。

リアル時間に追いついた場合やMaximum Allowed Timestepを超えてしまう場合はPhysicsのループを抜けて、Updateなどの処理が行われ、また、Physicsのループが行われる。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?