WisdomSoft - for your serial experiences.

描画処理

ゲーム画面を生成する Draw() メソッドを紹介します。ゲーム開発者は Draw() メソッドをオーバーライドし、ここにゲーム画面を描画するコードを記述します。

ゲーム画面の描画

Update() メソッドが呼び出され、ゲームのデータを更新した次に行うべき処理は、最新のゲームデータを基にしたゲーム画面の構築、すなわち描画処理です。ゲームは、描画が必要になると自身の Draw() メソッドを呼び出します。ゲームが独自の描画を行うには Draw() メソッドをオーバーライドします。原理は Update() メソッドと同じです。

Game クラス Draw() メソッド
protected virtual void Draw (GameTime gameTime)

このメソッドも、Update() メソッドと同じように gameTime パラメータに、現在のゲームの経過時間を受け取ります。 ゲームは、Draw() メソッドの中で GraphicsDevice オブジェクトを使った描画処理を行います。描画については、次章以降で詳細を説明します、この場では Draw() メソッドがどのように呼び出されているのかを調べましょう。ゲームループが開始されると、最初に Update() メソッドが呼び出されてデータが更新されます。その後、ゲームは最新のデータを描画するために Draw() メソッドを呼び出します。

コード1
using Microsoft.Xna.Framework;

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

    private int updateCount;
    private int drawCount;

    protected override void Update(GameTime gameTime)
    {
        updateCount++;
        Window.Title = "Update=" + updateCount + ", Draw=" + drawCount;
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        drawCount++;
        base.Update(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は、ゲーム起動後の Update() メソッドと Draw() メソッドの呼び出し回数をカウントし、タイトルバーに表示するプログラムです。起動時、Update() メソッドと Draw() メソッドは、ほぼ同じ実行回数でカウントされ続けるはずです。ただし、これらのメソッドは必ず交互に呼び出されるものではありません。

ゲームが固定ステップの場合、次の Update() メソッド呼び出しまでに時間の余裕があれば Draw() メソッドが呼び出されます。それでもまだ時間が余っている場合は、次の Update() 呼び出しまで待機します。しかし、Update() メソッドの呼び出しが遅れている場合、Draw() メソッドの呼び出しが省略されます。

通常、ゲームでかかる負荷の大部分は、描画処理に関連するコードです。そのため、ゲームが遅れている場合は Draw() メソッドを省略してデータの更新を優先します。Draw() メソッドが省略されると、その間はゲーム画面が更新されなくなりますが、Update() メソッドの呼び出しが省略されることはないので、内部のゲームデータは正常に更新され続けます。これが、ゲームでフレーム落ちが発生する理由です。

フレーム落ちの検出

固定ステップで要求されている更新が遅れ Draw() メソッドの呼び出しが省略されると、GameTime オブジェクトの IsRunningSlowly プロパティが true になります。このプロパティは、TargetElapsedTime プロパティに設定されている時間間隔よりも、ゲームループが遅れているかどうかを表します。

GameTime クラス IsRunningSlowly プロパティ
public bool IsRunningSlowly { get; set; }

このプロパティが true の場合、ゲームの実行が遅れています。ゲームの進行に必須ではない演出などを省略して、実行速度を速めるように工夫することができます。

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

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

    private int updateCount;
    private int drawCount;

    protected override void Update(GameTime gameTime)
    {
        updateCount++;
        Window.Title = "Slowly=" + gameTime.IsRunningSlowly +
            ", Update=" + updateCount + ", Draw=" + drawCount;
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        drawCount++;
        if (gameTime.TotalGameTime.Seconds % 2 == 0)
            Thread.Sleep(100);
        base.Draw(gameTime);
    }
}
実行結果
コード2 実行結果

コード2は、Draw() メソッドで 1 秒ごとに Thread クラスの Sleep() メソッドを使って意図的にプログラムを停止することで、描画処理で強い負荷がかかった場合の Update() メソッドと Draw() メソッドの呼び出しを再現します。

Update() メソッドで IsRunningSlowly プロパティの値と、Update() メソッド、Draw() メソッドそれぞれの呼び出し回数をタイトルバーに表示しています。ゲーム起動後の経過時間が偶数秒のとき、Draw() メソッドはスレッドを 100 ミリ秒停止します。この影響を受けて Update() メソッドの呼び出しが遅れ、IsRunningSlowly プロパティが true になることが確認できます。