Version: 2019.3
言語: 日本語
イベント関数
ゲームオブジェクトの作成および削除

タイムとフレームレートの管理

Update 関数を使用すると、定期的にスクリプトからの入力やその他のイベントをモニターして、適切な処置を取ることができます。例えば、Forward キーが押されるとキャラクターが動くとします。このような時系列のアクションを取り扱う場合、ゲームのフレームレートは一定ではなく、 Update 関数の呼び出しの時間的な間隔も一定ではないということに気を付けてください。

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

//C# の例
using UnityEngine;
using System.Collections;

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


//JS の例
var distancePerFrame: float;

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


//JS の例
var distancePerSecond: float;

function Update() {
    transform.Translate(0, 0, distancePerSecond * Time.deltaTime);
}

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

固定時間ステップ

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

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

最大許容時間ステップ

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

当然、CPU のパワーは非常に限られていますが、Unity は物理演算にかかる時間を効果的に遅くしてフレーム処理を追いつかせることができます。Maximum Allowed Timestep 設定 (Time ウィンドウ内) は、Unity が物理演算を処理するのに費やす時間と、特定のフレーム更新中に FixedUpdate の呼び出しを行う時間の制限を設定します。フレームの更新が Maximum Allowed Timestep よりも処理に時間がかかる場合、物理エンジンは “時間を停止” し、フレーム処理が追いつくようにします。フレームの更新が終了すると、物理演算の効果は停止してから時間が経過していないかのように再開します。この結果、リジッドボディは通常のようにリアルタイムで完全には動かず、わずかに減速します。ただし、物理効果の “クロック” は、それが正常に動いているかのように追跡します。物理時間が遅くすることは、一般的に目立つものではなく、ゲームのパフォーマンスに対して許容範囲の犠牲です。

タイムスケール

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

//JS の例
function Pause() {
    Time.timeScale = 0;
}

function Resume() {
    Time.timeScale = 1;
}

キャプチャーフレームレート

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

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

//C# の例
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 () {
        // ファイル名をフォルダー名に追加 (形式は "0005 shot.png")
        string name = string.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );
        
        // 指定したファイルにスクリーンショットをキャプチャ
        Application.CaptureScreenshot(name);
    }
}

//JS の例
//スクリーンショットシーケンスとしてフレームをキャプチャ
//画像はPNGファイルとしてフォルダーに格納。これらの画像は
// 画像ユーティリティソフトウェア (QuickTime Pro など) で動画と統合されます。
// スクリーンショットを格納するフォルダー。
// フォルダーが既存の場合、数を加え空のフォルダーを作ります。
var folder = "ScreenshotFolder";
var frameRate = 25;


function Start () {
    // // 再生のフレームレートを設定 (これ以降は、実際の時間はゲームの時間と関連しません)
    Time.captureFramerate = frameRate;

    // フォルダーを作成
    System.IO.Directory.CreateDirectory(folder);
}

function Update () {
    // ファイル名をフォルダー名に追加 (形式は "0005 shot.png")
    var name = String.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );

    // 指定したファイルにスクリーンショットをキャプチャ
    Application.CaptureScreenshot(name);
}

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

イベント関数
ゲームオブジェクトの作成および削除