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

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

Time クラス

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

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

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

Time.time returns the amount of time since your project started playing.

Time.deltaTime returns the amount of time that elapsed since the last frame completed.

Time.timeScale represents the rate at which time elapses. You can read this value, or set it to control how fast time passes, allowing you to create slow-motion effects.

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

Framerate Management

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);
    }
}

しかし、フレーム時間が一定でないので、オブジェクトは不規則な速度で移動するように見えます。フレーム時間が 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 で与えられていることに注意してください。フレームレートの変化に応じて移動距離が変わるため、オブジェクトの速度は一定になります。

固定時間ステップ

メインフレームの更新とは異なり、Unity の物理システムは、固定された時間ステップで更新されます。これはシミュレーションの正確さと一貫性にとって重要です。物理計算の更新開始時に、Unity は最後の物理計算の更新が終了する固定の時間ステップ値を時間に加えてアラームを設定します。物理システムは、アラームが切れるまで計算を実行します。

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

Maximum Allowed Timestep

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

当然、CPU のパワーは非常に限られていますが、Unity は物理演算にかかる時間を効果的に遅くしてフレーム処理を追いつかせることができます。Maximum Allowed Timestep 設定 (Time ウィンドウ内) は、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