WisdomSoft - for your serial experiences.

フレームの計測

1秒間に何回ゲーム画面が更新されたか、すなわち Draw() メソッドが何回実行されたかを計測することで、ゲームのパフォーマンスを計ることができます。

描画パフォーマンス

ゲームの実行環境のパフォーマンスの主要な指標に fps (Frame Per Second) があります。fps は、1 秒間の間に描画したゲーム画面の枚数のことを表すもので、XNA Framework では Draw() メソッドが 1 秒間の間に呼び出された回数に相当します。ゲームに限らず、動画などのマルチメディア関連のベンチマークで重要視される値です。一定以上の fps が安定して計測されていれば、ゲーム画面はなめらかに動いて見えます。逆に、fps が低ければ、映像はカクカクした不自然な動きに見えます。

ゲーム開発者の目標は、常に 60 fps 以上、すなわち 1 秒間の間に 60 回以上 Update() メソッドと Draw() メソッド呼び出せるパフォーマンスを維持し続けることです。この 60 fps は、XNA Framework が既定で設定している固定ステップに一致します。ゲームのために書かれるコードのうち、パフォーマンスに重大な影響を与えるのはゲームループ内で実行されるコードです。ゲームは、1 フレームを構築する工程で、データ処理、描画、入力処理、サウンド、ネットワーク、人工知能などの処理を実行します。これを 1 秒間に 60 回実行できるパフォーマンスを維持する必要があるのです。

コードを最適化するタイミングは開発者のセンスですが、ゲームが目標のパフォーマンスを維持しているかどうかは、常に意識するべきです。そのためにはベンチマークが必要です。固定ステップのゲームで fps を計測するには、ゲーム時間ではなく、現実の時間で 1 秒間の Draw() メソッドの呼び出し回数を観測する必要があります。

固定ステップの場合、パフォーマンスに余裕があっても設定されている上限以上の fps は出ることはないので注意てください。最大 fps をベンチマークとして計測したい場合は、可変ステップにする必要があります。ただし、GraphicsDeviceManager をインスタンス化し GraphicsDevice オブジェクトを保有している場合、可変ステップでも fps の上限が固定されるので注意してください。これは、GraphicsDeviceManager に設定されている SynchronizeWithVerticalRetrace プロパティが影響しています。

GraphicsDeviceManager クラス SynchronizeWithVerticalRetrace プロパティ
public bool SynchronizeWithVerticalRetrace { get; set; }

これは、垂直同期 (V-Sync) と呼ばれるもので、GraphicsDevice オブジェクトが指すディスプレイが 1 秒間に画面を書き換える回数に Draw() メソッドの呼び出しを同期させる機能です。

ディスプレイが画面を更新する速度は、リフレッシューレートと呼ばれています。垂直同期が有効の場合 Draw() メソッドを呼び出したあと、次の画面更新まで垂直同期待ちが発生します。このプロパティが true の場合、ゲームはリフレッシュレートに合わせて描画を行います。ベンチマークで、実行環境の最大 fps を調べるには、可変ステップにすることに加え、垂直同期を行わないように SynchronizeWithVerticalRetrace プロパティに false を設定する必要があります。

コード1
using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        graphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
        IsFixedTimeStep = false;
    }

    private DateTime prevTime;
    private int framePerSec;

    protected override void Initialize()
    {
        prevTime = DateTime.Now;
        base.Initialize();
    }

    protected override void Draw(GameTime gameTime)
    {
        framePerSec++;
        DateTime now = DateTime.Now;
        
        TimeSpan t = now - prevTime;
        if (t.TotalMilliseconds >= 1000)
        {
            Window.Title = framePerSec + "fps";
            Debug.WriteLine(framePerSec + "fps");
            framePerSec = 0;
            prevTime = now;
        }
        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は垂直同期を無効にした可変ステップのゲームで、可能な限り Update() メソッドと Draw() メソッドを呼び出し続けるように設定しています。Draw() メソッドでは、framePerSec フィールドの値をインクリメントし、Draw() メソッドが呼び出された回数をカウントします。

直前の計測時間を prevTime フィールドに保存し、現在時刻と prevTime フィールドの差が 1000 ミリ秒を超えた時点で、それまでカウントした framePerSec フィールドの値をタイトルバーに表示します。こうすることで、常に最新の 1 秒間の実フレーム数を表示できます。