WisdomSoft - for your serial experiences.

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

固定された間隔でゲーム画面を描画する固定ステップと、性能の限り連続でゲーム画面を描画する可変ステップについて説明します。また、それぞれのモードに切り替える方法を紹介します。

固定ステップ

デフォルトでは、固定ステップと呼ばれる一定の間隔でゲームのデータを更新する状態に設定されています。前述したように、ゲーム時間の 1 秒間に約 60 回 Update() メソッドが呼び出されてデータを更新します。こうすることで、実行するコンピュータのパフォーマンスに影響されることなく、一定の速度でゲームを進行させることができます。

この固定ステップによる Update() メソッドの呼び出しは、Game クラスの TargetElapsedTime プロパティの影響を受けます。

Game クラス TargetElapsedTime プロパティ
public TimeSpan TargetElapsedTime { get; set; }

このプロパティは、固定ステップで Update() メソッドを呼び出す時間間隔を表す TimeSpan を提供します。このプロパティに、任意の TimeSpan を設定することで、Update() メソッドの呼び出し間隔を変更できます。ゲーム更新の粒度を上げたい場合は、デフォルトよりも短い間隔に設定します。逆に、粒度を下げたい場合は、長い間隔に設定します。

コード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();
    }

    public Test()
    {
        TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 100);
    }

    private double t;
    private int? latestUpdatePerSec;
    private int currentUpdatePerSec;

    protected override void Update(GameTime gameTime)
    {
        currentUpdatePerSec++;
        t += gameTime.ElapsedGameTime.TotalMilliseconds;
        Window.Title = (latestUpdatePerSec == null ? "計測中..." : latestUpdatePerSec + "/s") +
            ", ElapsedGameTime=" + gameTime.ElapsedGameTime;

        if (t >= 1000)
        {
            latestUpdatePerSec = currentUpdatePerSec;
            currentUpdatePerSec = 0;
            t = 0;
            Debug.WriteLine("Update per sec=" + latestUpdatePerSec);
        }
        base.Update(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は、コンストラクタで TargetElapsedTime プロパティの値を変更し、100 ミリ秒間隔で Update() メソッドを呼び出すように設定しています。この設定が正しく機能していれば、1 秒間に 10 回 Update() メソッドが呼び出されることになります。実行結果のように、1 秒間の間に Update() メソッドが実行された回数と、最後の Update() メソッドの呼び出しからの経過時間を表示します。Update() メソッドが 100 ミリ秒間隔で呼び出され、1 秒間の間に 10 回実行されていることが確認できます。

1 秒間の間に呼び出された Update() メソッドの回数を数えるために、Update() メソッドが呼び出されるたびに currentUpdatePerSec フィールドの値をインクリメントしています。この値は、1 秒が経過した時点で初期化する必要があるので ElapsedGameTime プロパティから得られる前回の Update() メソッドからの経過時間をミリ秒単位で t フィールドに加算します。t フィールドが 1000 を超えた時点で 1 秒経過したと判断し、currentUpdatePerSec フィールドの値を latestUpdatePerSec フィールドに代入します。この latestUpdatePerSec フィールドの値が、最も新しい 1 秒間で Update() メソッドが呼び出された回数を表します。その後、currentUpdatePerSec フィールドと t フィールドを 0 に初期化しています。

可変ステップ

可能な限り Update() メソッドを呼び出し続けることを可変ステップと呼びます。可変ステップは、TargetElapsedTime プロパティの設定を無視して、可能な限り連続で Update() メソッドを呼び出し続けます。設定を可変ステップに変更するには IsFixedTimeStep プロパティを使います。

Game クラス IsFixedTimeStep プロパティ
public bool IsFixedTimeStep { get; set; }

このプロパティの値が true の場合は固定ステップ、そうでなければ可変ステップを表します。デフォルトでは固定ステップに設定されています。可変ステップに変更するには、このプロパティに false を設定します。

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

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

    public Test()
    {
        IsFixedTimeStep = false;
    }

    private double t;
    private int? latestUpdatePerSec;
    private int currentUpdatePerSec;

    protected override void Update(GameTime gameTime)
    {
        currentUpdatePerSec++;
        t += gameTime.ElapsedGameTime.TotalMilliseconds;
        Window.Title = (latestUpdatePerSec == null ? "計測中..." : latestUpdatePerSec + "/s") +
            ", ElapsedGameTime=" + gameTime.ElapsedGameTime;

        if (t >= 1000)
        {
            latestUpdatePerSec = currentUpdatePerSec;
            currentUpdatePerSec = 0;
            t = 0;
            Debug.WriteLine("Update per sec=" + latestUpdatePerSec);
        }
        base.Update(gameTime);
    }
}
実行結果
コード2 実行結果

コード2は、コンストラクタで IsFixedTimeStep プロパティを false に設定しています。そのため、このゲームのデータ更新は可変ステップです。実行環境が許す限りの速度で、連続して Update() メソッドを呼び出し続けます。Update() メソッドの内容は、コード1と変わりません。1 秒間に更新された回数をカウントし、タイトルバーに表示しています。実行結果をみると、Update() メソッドの呼び出し間隔が固定されていないことを確認できます。