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

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

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

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

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

## リアル（現実）時間

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

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

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

## ゲーム内時間

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


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


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

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


ゲーム時間は、

```C#
Time.timeScale = 2;
```

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

## よくあるコード例

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

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

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

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

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

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

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

## 公式ドキュメントでは？


https://docs.unity3d.com/ja/2019.4/ScriptReference/Time-time.html

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

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


Time.realtimeSinceStartupの方は

https://docs.unity3d.com/ja/current/ScriptReference/Time-realtimeSinceStartup.html

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

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


# 検証コード

```C#

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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15024/44e7d59a-2c66-e089-b505-fd6a96935c35.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15024/bbd2edf0-4df5-5f00-477c-df6f567286e7.png)


実際では６秒経っています`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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15024/ae31571f-f900-ded0-b6b9-4be7f7356175.png)

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15024/b4de8b5b-c418-8873-6c32-3a739660304a.png)

こちらは`Fixed　Timestep`の0.02です。


# 実際の動き

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

https://docs.unity3d.com/ja/2021.1/Manual/ExecutionOrder.html


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

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


以後、数値にしたほうがわかりやすいので、 `0.02`は`Fixed　Timestep`のことであり、 `0.3333`は`Maximum 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`が２倍になり、
`Fixed　Timestep`自体は変わらず、

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

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

## 同じフレームでの値

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


```C#
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15024/f3c8ca3a-ab45-3b21-b814-64c3b21d4813.png)

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


# 結論

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

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

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

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

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

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