Version: 2022.1
言語: 日本語
ScriptableObject
重要なクラス - Mathf

重要なクラス - Time

Unity の Time クラスは、プロジェクトで時間関連の値を扱う重要な基本プロパティを提供します。

このページでは、Time クラスのよく使われるメンバーと、それらの関係性について説明します。Time クラスの各メンバーに関する個別の説明は、Time スクリプトのリファレンスページ を参照してください。

Time クラスにはいくつかのプロパティがあり、ゲームやアプリケーションの実行中に経過する時間を測定するための数値を提供します。以下はその例です。

  • Time.time は、プロジェクトが再生を開始してからの時間 (秒単位) を返します。
  • Time.deltaTime は、最後のフレームが完了してから経過した時間 (秒単位) を返します。この値は、ゲームやアプリケーションの動作中の秒ごとのフレーム数 (FPS) により変化します。

Time クラスには、時間の経過を制御したり制限したりするためのプロパティも用意されています。

  • Time.timeScale は、時間が経過する速度を制御します。この値を読み取ったり、設定して時間が経過する速さを制御し、スローモーションのような効果を生み出すことができます。
  • Time.fixedDeltaTime は、Unity の固定時間ステップのループの間隔を制御します (物理演算、決定論的な時間ベースのコードを書きたい場合に使用されます)。
  • Time.maximumDeltaTime は、上記の “deltatime” プロパティによってエンジンが時間の経過を報告する上限を設定します。

可変時間ステップと固定時間ステップ

Unity には、時間を追跡するシステムとして、各ステップ間の時間が可変のものと、各ステップ間の時間が固定のものとがあります。

可変時間ステップシステムは、画面にフレームを描画し、フレームごとに 1 回アプリケーションやゲームのコードを実行する繰り返しの処理を操作します。

固定時間ステップシステムは、各ステップごとにあらかじめ定義された量で前進し、視覚的なフレーム更新とは連動していません。一般的には、固定時間ステップのサイズによって指定された割合で実行する物理システムに関連付けられますが、必要に応じて固定時間ステップごとに独自のコードを実行することもできます。

可変フレームレート管理

ゲームやアプリケーションのフレームレートは、各フレームの表示とコードの実行にかかる時間によって異なります。これは、実行するデバイスの性能に影響され、また、表示されるグラフィックスの複雑さや各フレームに必要な計算の量によっても異なります。例えば、あるゲームは 100 のキャラクターがアクティブで画面上にある場合、1 つだけしかない場合にくらべて遅いフレームレートで実行する可能性があります。この可変レートは、しばしば “フレームレート” (FPS) と呼ばれます。

品質設定 または Adaptive Performance パッケージ によって特に制約がない限り、Unity は可能な限り最速のフレームレートでゲームやアプリケーションを実行しようとします。各フレームで何が起こるかの詳細は、実行順序図 の “Game logic” と書かれたセクションで見ることができます。

Unity には、フレームごとに独自のコードを実行するためのエントリーポイントとして、Update メソッドが用意されています。例えば、ゲームキャラクターの Update メソッドで、ジョイパッドからのユーザー入力を読み取り、キャラクターを一定量前進させることができます。このような時間ベースのアクションを処理する際には、ゲームのフレームレートが変化する可能性があるため、Update の呼び出し間の時間の長さも変化する場合があることに注意してください。

フレームごとに、徐々に前方にオブジェクトを動かす作業を考えてみましょう。最初は、フレームごとに一定の距離ずつオブジェクトを移動しているように見えるかもしれません。

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public float distancePerFrame;
    
    void Update() {
        transform.Translate(0, 0, distancePerFrame); // this is incorrect
    }
}

ただし、このコードでは、フレームレートが変化すると、オブジェクトの見かけの速度も変化します。ゲームが毎秒 100 フレームで実行される場合、オブジェクトは distancePerFrame を毎秒 100 回動かします。しかし、フレームレートが 60 フレーム/秒になると (例えば、CPU 負荷のため)、1 秒間に 60 回しか前に進まないので、同じ時間でより短い距離しか移動できません。

ほとんどの場合、これは好ましくありません。特にゲームやアニメーションではそうです。ゲーム内のオブジェクトは、フレームレートに関係なく、安定した予測可能な速度で動くことが望ましいことがはるかに一般的です。この解決策は、各フレームの移動量を各フレームの経過時間によってスケールすることです。これは、Time.deltaTime プロパティから読み取ることができます。

//C# サンプルスクリプト
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public float distancePerSecond;
    
    void Update() {
        transform.Translate(0, 0, distancePerSecond * Time.deltaTime);
    }
}

移動は distancePerFrame ではなく distancePerSecond で与えられていることに注意してください。フレームレートの変化に応じて移動距離が変わるため、オブジェクトの速度は一定になります。

ターゲットプラットフォームによっては、Application.targetFrameRate または QualitySettings.vSyncCount のいずれかを使用して、アプリケーションのフレームレートを設定することができます。詳細については、Application.targetFrameRate API documentation を参照してください。

固定時間ステップ

メインフレームの更新とは異なり、Unity の物理システムは固定時間ステップで動作します。これはシミュレーションの精度と一貫性のために重要です。各フレームの開始時に、Unity は現在の時間に追いつくために必要なだけの固定の更新を実行します。固定更新サイクルで発生することの詳細は、実行順序図 の “Physics” と書かれたセクションで見ることができます。

また、必要であれば、固定時間ステップに同期して独自のコードを実行することもできます。これは、Rigidbody力を適用 するなど、独自の物理関連コードを実行する場合に最もよく使用されます。Unity は FixedUpdate メソッドをエントリーポイントとして提供し、固定時間ステップごとに独自のコードを実行します。

fixedDeltaTime プロパティは、Unity の固定時間ステップのループ間隔を制御し、秒数で指定します。例えば、0.01 という値は、各固定時間ステップが 100 分の 1 秒であることを意味し、1 秒あたり 100 回の固定時間ステップがあることになります。

ゲームやアプリケーションが、1 秒につき固定時間ステップよりも高いフレームレートで動作している場合は、各フレームの継続時間が 1 つの固定時間ステップの継続時間よりも短いことを意味します。そのため、Unity はフレームごとに 0 回または 1 回の固定物理演算の更新を行います。例えば、固定時間ステップ値が 0.02 の場合、1 秒間に 50 回の固定の更新が行われます。ゲームやアプリケーションが毎秒約 60 フレームで動作する場合、10 フレームに 1 回程度の割合で、固定更新が行われないフレームが発生します。

60 FPSで動作する更新と 1 秒間に 50 回実行する FixedUpdate を表示した例。時々、対応する FixedUpdate がなしでフレーム更新が発生 (黄色で表示) することがあります。
60 FPSで動作する更新と 1 秒間に 50 回実行する FixedUpdate を表示した例。時々、対応する FixedUpdate がなしでフレーム更新が発生 (黄色で表示) することがあります。

ゲームやアプリケーションが固定時間ステップの値よりも低いフレームレートで動作している場合は、各フレームの継続時間が 1 つの固定時間ステップよりも長いことを意味します。これを考慮して、Unity は各フレームで 1 つ以上の固定更新を実行し、物理シミュレーションが最後のフレームから経過した時間に追いつくようにします。例えば、固定時間ステップ値が 0.01 の場合、1 秒間に 100 回の固定の更新が行われます。ゲームやアプリケーションが毎秒約 25 フレームで動作する場合、フレームごとに 4 回の割合で固定更新を行います。高いフレームレートよりもより正確な物理演算をモデル化することが重要な、このようなシナリオが必要になる場合があります。

25 FPSで動作する更新と 1 秒間に 100 回更新を実行する FixedUpdate を表示する例。時々、1 フレーム中に 4 回の FixedUpdate (黄色で表示) が発生していることがわかります。
25 FPSで動作する更新と 1 秒間に 100 回更新を実行する FixedUpdate を表示する例。時々、1 フレーム中に 4 回の FixedUpdate (黄色で表示) が発生していることがわかります。

Time ウィンドウで、またはスクリプトから Time.fixedDeltaTime プロパティを使用して、固定時間ステップの継続時間を読み取ったり変更したりすることができます。

ノート: 時間ステップの値が低いと、物理演算の更新頻度が高くなり、より精密なシミュレーションが可能になるため、CPU の負荷が高くなります。

Unity の時間のロジック

以下のフローチャートは、Unity が 1 つのフレーム内の時間をカウントするために使用するロジックと、timedeltaTimefixedDeltaTimemaximumDeltaTime プロパティがどのように相互に関係しているかを示しています。

時間的なばらつきの制御と処理

上で説明したように、各フレーム間の経過時間にばらつきがある場合があります。

経過時間の変動はわずかです。例えば、1 秒間に 60 フレームで動作するゲームでは、1 秒間の実際のフレーム数はわずかに異なり、1 フレームは 0.016 秒から 0.018 秒の間で推移します。アプリケーションが重い計算やガベージコレクションを実行したり、フレームレートを維持するために必要なリソースが別のアプリケーションによって使用されている場合、より大きな変動が発生する可能性があります。

このセクションでは、以下のプロパティを説明するします。

これらのプロパティにはそれぞれ独自のスクリプト API ドキュメントページがありますが、その説明と出力を相互に関連させて見ることで、適切な使用方法を理解することができます。

Time.time はプレイヤーが開始してからの経過時間を示し、通常は連続的かつ安定的に上昇します。Time.deltaTime は最後のフレームからの経過時間を示し、ほぼ一定であることが理想的です。

これらの値はいずれも、アプリケーションやゲーム内での経過時間を主観的に測定したものです。つまり、適用する時間のスケーリングが考慮されます。例えば、Time.timeScale を 0.1 に設定すると、スローモーション効果 (通常の再生速度の10% を示す) を得られます。この場合、 Time.time が報告する値は、“実際” の時間の10% 増加します。10 秒後、Time.time の値は 1 増加します。アプリケーションの時間を原則または加速するだけでなく、Time.timeScale を 0 に設定してゲームを一時停止できます。この場合、 Update メソッドは引き続き呼び出されますが、 Time.time は全く増加せず、 Time.deltaTime は 0 になります。

これらの値は、Time.maximumDeltaTime プロパティの値によって固定されます。つまり、これらのプロパティで報告されるフレームレートの一時停止や変動の長さは、決して maximumDeltaTime を超えることはありません。例えば、1 秒の遅延が発生しても、maximumDeltaTime がデフォルト値の 0.333 に設定されていれば、Time.time は 0.333 だけ増加し、現実世界ではもっと時間が経過しているにもかかわらず、Time.deltaTime は 0.333 になります。

これらのプロパティのスケールされないバージョン (Time.unscaledTimeTime.unscaledDeltaTime) は、これらの主観的な変動や制限を無視し、どちらの場合も実際に経過した時間を報告します。これは、ゲームがスローモーションで再生されているときでも、固定の速度で応答する必要があるものすべてに便利です。この例として、UI インタラクションのアニメーションがあります。

下の表は、16 フレームが次々と経過し、途中 1 フレームで大きな遅延が発生した例です。これらの数字は、様々な Time クラスのプロパティが、このフレームレートの大きな変動に対してどのように報告し、対応するかを示しています。

フレーム unscaledTime time unscaledDeltaTime deltaTime smoothDeltaTime
1 0.000 0.000 0.018 0.018 0.018
2 0.018 0.018 0.018 0.018 0.018
3 0.036 0.036 0.018 0.018 0.018
4 0.054 0.054 0.018 0.018 0.018
5 0.071 0.071 0.017 0.017 0.018
6 0.089 0.089 0.018 0.018 0.018
7 0.107 0.107 0.018 0.018 0.018
8 (a) 1.123 (b) 0.440 (c) 1.016 (d) 0.333 (e) 0.081 (f)
9 1.140 0.457 0.017 0.017 0.066
10 1.157 0.474 0.017 0.017 0.056
11 1.175 0.492 0.018 0.018 0.049
12 1.193 0.510 0.018 0.018 0.042
13 1.211 0.528 0.018 0.018 0.038
14 1.229 0.546 0.018 0.018 0.034
15 1.247 0.564 0.018 0.018 0.031
16 1.265 0.582 0.018 0.018 0.028

フレーム 1 から 7 までは、1 秒間に約 60 フレームという安定した速度で動作しています。“time” と “unscaledTime” が共に着実に増加し、この例での timeScale が 1 に設定されていることを示しています。

そして、フレーム 8 (a) で、1 秒強の大きな遅延が発生します。これは、リソースの競合がある場合に発生する可能性があります。例えば、アプリケーション内のあるコードが、ディスクから大量のデータをロードしている間、メインプロセスをブロックしている場合です。

maximumDeltaTime 値より大きいフレーム遅延が発生すると、Unity は deltaTime によって報告される値、および現在の time に加えられた量を制限します。この制限は、時間ステップがその量を超えた場合に発生する可能性がある望ましくない副作用を回避することを目的とします。もし制限がなければ、フレームレートが急上昇したときに、deltaTime で動きがスケールされたオブジェクトは、ゲーム内の壁を “図らずも” 通過してしまったことでしょう。なぜなら、理論上、オブジェクトがあるフレームから次のフレームに移動できる距離に制限はないため、1 つのフレーム内で障害物とまったく交差することなく、障害物の一方の側から別の側にジャンプするできるからです。

maximumDeltaTime の値は、Time ウィンドウの Maximum allowed timestep で調整できます。また、Time.maximumDeltaTime プロパティでも調整できます。

デフォルトの maximumDeltaTime 値は 1 秒の 3 分の 1 (0.3333333) です。これは、動きが deltaTime で制御されるゲームでは、あるフレームから次のフレームへのオブジェクトの動きは、前のフレームから実際に経過した時間に関係なく、3 分の 1 秒でカバーできる距離に制限されることを意味します。

上の表のデータをグラフにすると、これらの時間のプロパティが互いの関係で、どのように動作するかを可視化できます。

上記のフレーム 8 で、unscaledDeltaTime (d) と deltaTime (e) では、報告された経過時間が異なっているのがわかります。フレーム 7 と 8 の間で実時間が 1 秒経過しているにもかかわらず、deltaTime は 0.333 秒しか報告されていません。これは、deltaTime が maximumDeltaTime の値に固定されるためです。

同様に、unscaledTime (b) は、実際の (固定されていない) 値が追加されたため、およそ 1 秒増加しましたが、time (c) は、小さい固定値だけ増加しています。time の値は経過した実際の時間には追いつかず、代わりに、遅延が maximumDeltaTime だけであったかのように動作します。

Time.smoothDeltaTime プロパティは、アルゴリズムに従ってすべての変動が平滑化された最近の deltaTime 値の近似値を報告します。これは、移動または他の時間ベースの計算で望ましくない大きなステップまたは変動を回避するための別のテクニックです。特に、maximumDeltaTime によって設定された閾値を下回る場合に有効です。平滑化アルゴリズムは将来の変動を予測することはできませんが、最近経過した deltatime 値の変動を滑らかにするために、報告された値に徐々に適用されます。そのため、報告される平均時間は実際の経過時間とほぼ同等に保たれます。

時間変化と物理システム

maximumDeltaTime 値は物理システムにも影響します。物理システムは fixedTimestep 値を使用して、各ステップでどれだけの時間をシミュレートするかを決定します。Unity は物理シミュレーションを経過した時間に合わせて最新の状態に保とうとし、前述のように、時にはフレームごとに複数の物理演算の更新を実行します。しかし、物理シミュレーションが、何らかの重い計算や遅延などによって大きく遅れてしまった場合、物理システムは現在の時間に追いつくために多くのステップを必要とすることがあります。このステップ数の多さは、それ自体がさらなる速度低下を引き起こす可能性があります。

このように、追いつこうとするための減速という循環的なフィードバックを避けるために、maxDeltaTime 値は、物理システムが任意の 2 つのフレーム間でシミュレーションを行う際の時間の制限としても機能します。

フレーム更新の処理が maximumDeltaTime よりも長くかかる場合は、物理エンジンは maximumDeltaTime を超える時間のシミュレーションを行おうとせず、フレーム処理が追いつけるようにします。フレーム更新が完了すると、物理演算は、停止してからまったく時間が経過していないかのように再開します。この結果、物理オブジェクトは通常のように完全にリアルタイムで動くことはなく、わずかに遅くなります。ただし、物理演算の “時計” は、通常通り移動しているかのようにそれらを追跡します。物理演算時間の遅延は通常は目立たず、ゲームパフォーマンスとの許容範囲の妥協策です。

タイムスケール

スローモーションのような特殊な時間の効果の場合、ゲームの時間の経過を遅くして、コード内のアニメーションと時間ベースの計算をより遅いペースで実行することは、有用である場合があります。さらに、ゲームを中断するときのように、完全にゲーム時間を凍結したい場合があるかもしれません。Unity には、リアルタイムと関連づけてゲーム時間をどのように進行させるかについて制御する Time Scale プロパティがあります。スケールを 1.0 に設定すると、ゲーム内の時間はリアルタイムに一致します。2.0 の値では Unity 内で 2 倍の速さで時間が経過します (つまり、アクションは高速化されます)。他方、0.5 の値ではゲームを半分の速度に遅くします。0 の値はゲーム内の時間を完全に停止します。タイムスケールは、実際に実行を遅くするわけではなく、単に Time.deltaTimeTime.fixedDeltaTime を通して Update と FixedUpdate 関数に知らせる時間ステップを変えているにすぎません。タイムスケールを縮小しても Update 関数は同じ頻度で呼び出されますが、各フレームの deltaTime の値は小さくなります。他のスクリプト関数はタイムスケールから影響を受けません。ですから、例えば、ゲームが停止している場合は、正常なインタラクションで GUI を表示できます。

Time ウィンドウは、グローバルにタイムスケールを設定できるプロパティを持っていますが、一般的に、ScriptRef:Time-timeScale プロパティを使用してスクリプトから値を設定すると、より便利です。

//C# スクリプトサンプル
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void Pause() {
        Time.timeScale = 0;
    }
    
    void Resume() {
        Time.timeScale = 1;
    }
}

フレームレートを取得

時間管理の特殊なケースとして、ゲームを動画として記録したい場合があります。画面の画像を保存する作業にかなり時間がかかるため、通常のゲームのフレームレートは低下し、動画にゲームの本当のパフォーマンスが反映されません。

動画の見栄えを良くするには、Capture Framerate プロパティを使用します。ゲームプレイを録画していない場合では、録画用のこのプロパティのデフォルト値は 0 です。プロパティの値を 0 以外に設定すると、ゲーム時間が遅くなり、フレーム更新が正確な一定間隔で発行されます。フレーム間の間隔は、1 / Time.captureFramerate です。 つまり、値を 5.0 に設定すると、更新は 5 分の 1 秒ごとに行われます。フレームレートへの要求が効果的に低減され、Update 関数でスクリーンショットを保存したり、他のアクションを取るための時間があります。

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    // Capture frames as a screenshot sequence. Images are
    // stored as PNG files in a folder - these can be combined into
    // a movie using image utility software (eg, QuickTime Pro).
    // The folder to contain our screenshots.
    // If the folder exists we will append numbers to create an empty folder.
    string folder = "ScreenshotFolder";
    int frameRate = 25;
        
    void Start () {
        // Set the playback frame rate (real time will not relate to game time after this).
        Time.captureFramerate = frameRate;
        
        // Create the folder
        System.IO.Directory.CreateDirectory(folder);
    }
    
    void Update () {
        // Append filename to folder name (format is '0005 shot.png"')
        string name = string.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );
        
        // Capture the screenshot to the specified file.
        Application.CaptureScreenshot(name);
    }
}

このテクニックを使うと動画は良くなりますが、ゲームを行うことがかなり難しくなる可能性があります。Time.captureFramerate の値を変えて、良いバランスを探してください。

ScriptableObject
重要なクラス - Mathf