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

重要なクラス - 時間とフレームレートの管理

Time クラス

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

Time スクリプトリファレンス のページでは各メンバーを詳しく説明しています。

ここでは、一般的で典型的な使用例をいくつか紹介します。

Time.time は、プロジェクトが再生を開始してからの時間を返します。

Time.deltaTime は、最後のフレームが完了してからの経過時間を返します。

Time.timeScale は、時間が経過する速度を表します。この値を読み取ったり、設定して時間が経過する速さを制御し、スローモーションのような効果を生み出すことができます。

時間関連のプロパティの全リストについては、Time のスクリプトリファレンスページ を参照してください。

Update 関数を使用すると、定期的にスクリプトからの入力やその他のイベントをモニターして、適切なアクションを行うことができます。例えば、Forward キーを押すとキャラクターが動くとします。このような時間に基づいたアクションを取り扱う場合、ゲームのフレームレートは一定ではなく、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
    }
}

しかし、フレーム時間が一定でないので、オブジェクトは不規則な速度で移動するように見えます。フレーム時間が 10 ミリ秒なら、オブジェクトは 1 秒に distancePerFrame の距離を 100 回前進します。しかし、フレーム時間が 25 ミリ秒に増加した場合 (例えば CPU 負荷のため) 1 秒に 40 回しか前進できず、進む距離が少なくなります。その解決方法は、フレーム時間に応じた移動の距離をスケーリングすることです。移動の距離は 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 で与えられていることに注意してください。フレームレートの変化に応じて移動距離が変わるため、オブジェクトの速度は一定になります。

固定時間ステップ (fixedDeltaTime)

更新とは異なり、Unity の物理システムは、シミュレーションの正確さと一貫性にとって重要な固定時間ステップ (fixed timestep) に有効です。物理計算の更新開始時に、Unity は現在の時刻に追いつくのに必要な物理計算の更新を行います。

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

ゲームやアプリケーションが固定時間ステップの値よりも低いフレームレートで動作している場合は、各フレームが 1 つの固定時間ステップの持続時間よりも長いことを意味します。そのようにして、物理シミュレーションが最後のフレームからの経過時間に “追いつく” ようにします。例えば、固定時間ステップ値が 0.01 の場合、1 秒間に 100 回の固定の更新が行われます。ゲームやアプリケーションが毎秒約 25 フレームで動作する場合、フレームごとに 4 回程度の割合で固定更新を行い、物理シミュレーション時間を現在のフレーム時間に合わせて最新に保ちます。

Time ウィンドウで固定時間ステップの値を変更でき、Time.fixedDeltaTime プロパティを使用して、スクリプトからそれを読み取ることができます。時間ステップの値が低いと、より頻繁に物理計算の更新が行われより正確なシミュレーションが可能ですが、CPU への付加が高くなります。物理エンジンに特に高精度を必要とする高い要求をしない限り、大抵、デフォルトの固定時間ステップを変更する必要はありません。

Unity の時間のロジック

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

最大許容時間ステップ (maximumDeltaTime)

非常に遅いフレームが発生すると、Maximum Allowed Timestep (最大許容時間ステップ、Time ウィンドウ内) は、非常に大きな deltaTime 値に起因する望ましくない状態を避けるために、以下のフレームの deltaTime の値を制限します。この値は、スクリプトを通して Time.maximumDeltaTime としてアクセスすることもできます。

例えば、アプリケーションが maxDeltaTime の値 0.333 で 60Hz で実行されている場合に、1 つの非常に遅いフレーム (2 秒) に遭遇した場合、以下の表のような動作になります。

フレーム unscaledTime time deltaTime smoothDeltaTime
1 0.000 3.000 0.014 0.014
2 0.014 3.015 0.014 0.014
3 0.028 3.028 0.014 0.014
4 (a) 0.042 3.043 0.014 0.014
5 2.062 (b) 3.376 (c) 0.333 (d) 0.078
6 2.082 3.396 0.020 0.066
7 2.096 3.410 0.014 0.056
8 2.110 3.424 0.014 0.048

上のデータは、最大許容時間ステップの値 (maximumDeltaTime) の影響を示しています。1 から 8 の番号がつけられた 8 つのフレームを示しています。この例のフレーム 4 (a) では速度の減退が発生し、他のフレームのように約 25 ミリ秒ではなく、フレームが完了するのに 2 秒かかる原因になっています。この影響は次のフレームの値に反映されています。一時停止は unscaledTime 値では約 2 の増分として確認できますが (b)、Time.time 値は 0.333 (c) だけ増加しています。これはこの例が実行されていたときの最大許容時間ステップの値だからです。このフレームの deltaTime 値も、最大許容時間ステップ (d) に固定されています。

最大許容時間ステップの値は、物理の時間管理にも影響します。固定時間ステップ は、リアルタイムで物理シミュレーションを正確に保ちます。しかし、ゲームで物理演算を多用する場合、場合によっては問題を引き起こす可能性があり、ゲーム時のフレームレートも低くなってしまいます (例えば、多くのオブジェクトが動いている場合など)。主フレームの更新処理は、通常の物理演算更新の間に “詰め込む” 必要があります。そのため、実行する処理が大量にある場合は、いくつかの物理演算更新を 1 つのフレーム内で行い、現際の時間に物理シミュレーションを間に合わせることが必要な場合があります。フレームタイム、オブジェクトの位置、他のプロパティーがフレームの開始時に凍結され、頻繁に更新される物理演算との同期状態ではなくなります。

CPU パワーが限られているということは、物理システムの処理能力にも限界があるということです。そこで Unity には、物理の時間を効果的に遅くして、フレーム処理が遅れた場合に追いつけるようにするオプションがあります。最大許容時間ステップは、フレーム更新中に Unity が物理演算と FixedUpdate の呼び出しの処理に費やす時間を制限します。

フレーム更新の処理が Maximum Allowed Timestep の処理よりも長くかかる場合は、物理エンジンは “時間を停止” し、フレーム処理が追いつけるようにします。フレーム更新が完了すると、物理演算は、停止してからまったく時間が経過していないかのように再開します。結果的には、リジッドボディは通常行うように、リアルタイムで完璧に移動することはできず、少し遅れてしまいます。ただし、物理演算の “時計” は、通常通り移動しているかのようにそれらを追跡します。物理演算時間の遅延は通常は目立たず、ゲームパフォーマンスとの許容範囲の妥協策です。

Time Scale

“bullet-time” のような特殊効果のために、ゲームの時間の経過を遅くして、アニメーションやスクリプトの応答を少ない割合で発生させることは、有効な場合があります。さらに、ゲームを中断するときのように、完全にゲーム時間を凍結したい場合があるかもしれません。Unity は、リアルタイムと関連づけてゲーム時間をどのように進行させるかについて制御する Time Scale プロパティを持っています。スケールを 1.0 に設定すると、ゲームの時間はリアルタイムに一致します。2.0 の値では Unity で 2 倍の速さで時間が経過します (つまり、アクションは、高速化されます)。他方、0.5 の値ではゲームを半分の速度に遅くします。0 の値は時間を完全に「停止」します。タイムスケールは、実際に実行を遅くするわけではなく、単に Time.deltaTimeTime.fixedDeltaTime を通して Update と FixedUpdate 関数に知らせる時間ステップを変えているにすぎません。Update 関数は、ゲーム時間が遅くなるとき通常より頻繁に呼び出される傾向がありますが、単に、各フレームに伝えられる デルタタイム の処理が減らされます。他のスクリプト関数はタイムスケールから影響を受けません。ですから、例えば、ゲームが停止している場合は、正常なインタラクションで GUI を表示できます。

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

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

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

Capture Framerate

ゲームをビデオとして記録したいケースは時間管理の非常に特別なケースです。画面を保存する作業にはかなりの時間がかかるため、通常のゲーム中にこれを行おうとすると、ゲームの通常のフレームレートは大幅に削減されます。これではゲームの真の性能を反映しない動画になってしまいます。

Unity はこの問題を回避することができる Capture Framerate プロパティを提供しています。プロパティの値が 0 以外の値に設定されている場合、ゲームタイムは遅くなり、フレーム更新は正確な一定の間隔で行われます。フレーム間の間隔は、1/(Time.captureFramerate) に等しく、5.0 に値が設定される場合、更新が 1/5 秒ごとに発生します。フレームレートへの要求を効果的に削減すると、Update 関数の時間で、スクリーンショットを保存したり、他の操作を実行できます。

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

public class ExampleScript : MonoBehaviour {
    // 連続のスクリーンショットとしてフレームをキャプチャする。
    // 画像は PNG ファイルとしてフォルダーに保存されます - これらは、
    // 画像ユーティリティソフトウェア (QuickTime Pro など) を使用してムービーに組み合わせることができます。
    //スクリーンショットを格納するフォルダー。
    // フォルダーが存在する場合は、番号を追加して空のフォルダを作成します。
    string folder = "ScreenshotFolder";
    int frameRate = 25;
        
    void Start () {
        // 再生フレームレートを設定します (この後、実際の時間はゲーム時間とは関係しなくなります)。
        Time.captureFramerate = frameRate;
        
        // フォルダーを作成
        System.IO.Directory.CreateDirectory(folder);
    }
    
    void Update () {
        // フォルダ名にファイル名を追加 (format is '0005 shot.png"')
        string name = string.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );
        
        // スクリーンショットを指定したファイルにキャプチャします。
        Application.CaptureScreenshot(name);
    }
}

一般的にこの技術を使用して動画を記録するのは非常に有効のようですが、著しく遅くなると、ゲームをプレイすることが難しくなります。テストプレーヤーの仕事を困難にしないよう十分なレコーディング時間を可能にするために Time.captureFramerate 値を試してみると良いでしょう。

ScriptableObject
重要なクラス - Mathf