描画処理
ゲーム画面の描画
Update() メソッドが呼び出され、ゲームのデータを更新した次に行うべき処理は、最新のゲームデータを基にしたゲーム画面の構築、すなわち描画処理です。ゲームは、描画が必要になると自身の Draw() メソッドを呼び出します。ゲームが独自の描画を行うには Draw() メソッドをオーバーライドします。原理は Update() メソッドと同じです。
protected virtual void Draw (GameTime gameTime)
このメソッドも、Update() メソッドと同じように gameTime パラメータに、現在のゲームの経過時間を受け取ります。 ゲームは、Draw() メソッドの中で GraphicsDevice オブジェクトを使った描画処理を行います。描画については、次章以降で詳細を説明します、この場では Draw() メソッドがどのように呼び出されているのかを調べましょう。ゲームループが開始されると、最初に Update() メソッドが呼び出されてデータが更新されます。その後、ゲームは最新のデータを描画するために Draw() メソッドを呼び出します。
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は、ゲーム起動後の Update() メソッドと Draw() メソッドの呼び出し回数をカウントし、タイトルバーに表示するプログラムです。起動時、Update() メソッドと Draw() メソッドは、ほぼ同じ実行回数でカウントされ続けるはずです。ただし、これらのメソッドは必ず交互に呼び出されるものではありません。
ゲームが固定ステップの場合、次の Update() メソッド呼び出しまでに時間の余裕があれば Draw() メソッドが呼び出されます。それでもまだ時間が余っている場合は、次の Update() 呼び出しまで待機します。しかし、Update() メソッドの呼び出しが遅れている場合、Draw() メソッドの呼び出しが省略されます。
通常、ゲームでかかる負荷の大部分は、描画処理に関連するコードです。そのため、ゲームが遅れている場合は Draw() メソッドを省略してデータの更新を優先します。Draw() メソッドが省略されると、その間はゲーム画面が更新されなくなりますが、Update() メソッドの呼び出しが省略されることはないので、内部のゲームデータは正常に更新され続けます。これが、ゲームでフレーム落ちが発生する理由です。
フレーム落ちの検出
固定ステップで要求されている更新が遅れ Draw() メソッドの呼び出しが省略されると、GameTime オブジェクトの IsRunningSlowly プロパティが true になります。このプロパティは、TargetElapsedTime プロパティに設定されている時間間隔よりも、ゲームループが遅れているかどうかを表します。
public bool IsRunningSlowly { get; set; }
このプロパティが true の場合、ゲームの実行が遅れています。ゲームの進行に必須ではない演出などを省略して、実行速度を速めるように工夫することができます。
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は、Draw() メソッドで 1 秒ごとに Thread クラスの Sleep() メソッドを使って意図的にプログラムを停止することで、描画処理で強い負荷がかかった場合の Update() メソッドと Draw() メソッドの呼び出しを再現します。
Update() メソッドで IsRunningSlowly プロパティの値と、Update() メソッド、Draw() メソッドそれぞれの呼び出し回数をタイトルバーに表示しています。ゲーム起動後の経過時間が偶数秒のとき、Draw() メソッドはスレッドを 100 ミリ秒停止します。この影響を受けて Update() メソッドの呼び出しが遅れ、IsRunningSlowly プロパティが true になることが確認できます。