Unityの時間に関することは2種類あります
時間としては、リアル(現実)時間とゲーム内時間があります。
1倍のままでマシンの負荷がかからなければ、ゲーム内時間とリアル時間はほぼ同一です。
しかし、3Dモデルなどの読み込みや、そのマシンで別の作業があったりして負荷がかかるとずれる時があります。
ちなみにこの記事は、むしろTime.time
とTime.deltaTime
を使ったほうが良いという記事です。
ただ、リアルな時間計測では使わないでくださいというものです。
リアルタイム対戦する場合は、マシンごとの負荷に合わせるよりリアル時間に合わせたほうが良いときもあるのでTime.time
とTime.deltaTime
を使わないほうが良いときもあります。
リアル(現実)時間
これは文字通り現実の時間です。
Time.realtimeSinceStartup
で取得できるものや
C#標準のDateTime
やStopwatch
はリアル時間を使っています。
これはマシンの性能や、負荷がかかるようなことをしても現実世界の経過時間のままです。
ゲーム内時間
ゲーム内で使われる時間です。
物理挙動などもこの時間をベースに動いています。
簡単に言うと、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は負荷がかかっている処理の代替です。
さて実際のログはこの様になってなりました。
実際では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」です。
これは1フレームゲーム内時間の最大を0.33333
とするというもので、
ずっと負荷がかかり続けていると Update
が呼ばれなくなったりキーボードの状態などが取らなくなるので、せめて、0.33333
の秒(ゲーム内時間)が経ったときにUpdate
などが呼ばれるようになっているようです。
先程のログで、Update関数が実行されるまでに FixedUpdate
がたくさん呼ばれております。
こっちは0.02ずつ増えています。
こちらはFixed Timestep
の0.02です。
実際の動き
これらの動きを理解するためにはまずこの「スクリプトライフサイクルのフローチャート」を理解する必要があります。
Physicsのところにループがあるのがポイントです。
具体的なドキュメントがないので推測にはなりますが、下のように動いているのではないかと思われます。
以後、数値にしたほうがわかりやすいので、 0.02
はFixed Timestep
のことであり、 0.3333
はMaximum Allowed Timestep
のことです。
FixedUpdate
の後にリアル時間に追いつくようにゲーム内時間Time.time += 0.02
され時間が経過するようになっている。
-
Physicsフェーズの最初でリアル時間の経過が0.02秒未満の場合、ループを抜けUpdateなどが呼ばれる。
そもそも、FixedUpdate
が呼ばれないこともある。 -
リアル時間の経過が
0.02
秒以上の場合、physicsのループを行い、FixedUpdate
が何度も呼ばれ、なるべくリアル時間に近づく。
physicsのループが0.33333
秒経っても抜けない場合は、抜けてUpdate
などの処理をする。このとき特にリアル時間とゲーム内時間がずれる。
また、リアルな時間とずれていても追いつきそうなら追いついていきますので、負荷が少なくなってもいつまででもずれているというわけではありません。
ちなみに、Time.timeScale=2
などにすると、Maximum Allowed Timestep
が2倍になり、
Fixed Timestep
自体は変わらず、
2.の部分が「リアル時間の経過が0.01
秒以上」などのようになり FixedUpdate
の関数が2倍呼ばれるようになるようです。
(FixedUpdate
1回分の時間経過は変わらない)
このとき
Time.unscaledTime += 0.01
のようになってtimeScaleの逆数が係数としてかけられる。
同じフレームでの値
ちなみに、Time.time
やTime.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));
}
}
Time.time
は同じフレーム内で変わらない、
Time.realtimeSinceStartup
は 同じフレーム内でも変わるということを確認しました。
結論
ドキュメントで「ゲーム開始からの時間(秒)」などと書かれているものはゲーム時間のことです。
「リアルタイム」と書かれていたらリアル時間です。
Time.time
がリアル時間ではないと言って、使わないのではなくぜひ使ってください。
ですが、現実の時間ではないことには気をつけてください。
FixedUpdate
が一回終わるごとにゲーム内時間がFixed Timestep
分増え、何度も呼ばれることによりリアル時間に追いつこうとする、
FixedUpdate
関数が終わるごとに物理シミュレーションされる。
「FixedUpdate
は固定の時間で呼ばれる」とはあるが、確かにリアル時間に合わせようとはするが、リアル時間で固定されているわけではなくゲーム内時間の 0.02
ごとに呼ばれる。
リアル時間からラグは起こるかもしれないが、ゲーム自体は崩壊しないようにしている。
処理が重いことがあると ゲーム内時間経過 != リアル時間経過
になる。 負荷が軽減されるようになると、リアル時間に戻ろうとする。
リアル時間に追いついた場合やMaximum Allowed Timestep
を超えてしまう場合はPhysicsのループ
を抜けて、Update
などの処理が行われ、また、Physicsのループ
が行われる。