ゲームループと更新処理
ゲームループ
これまでの、ゲームの起動から初期化までの工程が終了すると、ゲームループと呼ばれるゲーム本体の処理に制御が移ります。ゲームループで行われる作業は、大きく 2 つに分かれます。
1 つは、ゲームのデータを更新する処理です。ゲームデータの内容は、ゲームの種類によって異なるため、開発者が任意で用意します。時間の経過やユーザーの入力などに反応して、ゲームのデータを更新します。例えば、コントローラやキーボードの入力に反応して、画面上のキャラクターを動かすには、データの更新処理の過程で入力状態を調べ、キャラクターの座標などを変更します。
もう 1 つは、ゲームの描画です。描画処理では、主に GraphicsDevice オブジェクトを通してゲーム画面上に任意の画像や 3D モデルを表示します。通常、描画する内容はゲームデータに基づき、直前のデータ更新処理の結果から、最新のゲームの状態を画面に描画することが目的となります。
ゲームループでは、このデータ更新処理と描画処理が、非常に短い間隔で交互に繰り返し実行され続けます。デフォルトの設定では、これらの処理を 1 秒間に約 60 回実行します。一般の .NET Framework アプリケーション開発者の方は、驚かれるかもしれません。ゲームのアプリケーション開発モデルは、描画が必要なときや、入力された時に、特定のメソッドが実行されるイベント駆動型ではないのです。常にゲームループで処理が行われ、プログラムがすべての流れを管理します。
データの更新
ゲームが起動し、ゲームループの処理に入ると、データを更新するために Update() メソッドが呼び出されます。Update() メソッドの役割は、状況に応じたゲームデータの更新など、ゲームのロジックを処理することです。ゲーム独自の処理を追加するには Update() メソッドをオーバーライドしてください。このメソッドは、初期化処理が終わり BeginRun() メソッドが呼び出された後で呼び出されます。
protected virtual void Update (GameTime gameTime)
このメソッドは、繰り返し連続で呼び出され続ける点に注意してください。このメソッドの処理の中で、寿命の短いインスタンスを生成する、ガベージコレクションが頻繁に発生してパフォーマンスに深刻な影響を与えます。また、Update() メソッドの処理が詰まると、ゲームの進行そのものに影響を与えることになります。
gameTime パラメータには、現在のゲーム時間の情報を提供する Microsoft.Xna.Framework.GameTime クラスのオブジェクトが渡されます。このオブジェクトを調べることによって、時間の経過に応じてゲームを進行させることができるようになります。
public class GameTime
このクラスは、ゲーム開始以降の時間を表す TotalGameTime プロパティを提供しています。
public TimeSpan TotalGameTime { get; set; }
TotalGmaeTime プロパティは、ゲームが実行されていた時間を表すものです。ゲームが停止していた時間は含まれません。ゲーム内の固定的なステップで進行させたい場合は TotalGameTime プロパティを使うとよいでしょう。一方、実時間で時間経過を計測したい場合は .NET Framework の DateTime クラスを利用します。
using System; using Microsoft.Xna.Framework; public class TestGame : Game { public static void Main(string[] args) { using (Game game = new TestGame()) game.Run(); } protected override void Update(GameTime gameTime) { Window.Title = "GameTime=" + gameTime.TotalGameTime.ToString() + ", RealTime=" + DateTime.Now.ToString(); base.Update(gameTime); } }
コード1は、ウィンドウのタイトルバーに、Update() メソッドが呼び出された時のゲームの経過時間を、ゲーム時間と実際の時間で、それぞれ表示するプログラムです。実行すれば、Update() メソッドが連続で呼び出されていることが確認できます。
Update() メソッドの処理は、ゲームの心臓部分となります。このメソッドの呼び出しが遅延すると、ゲームの処理そのものに影響を与えることになります。ゲームを開発するときは、Update() メソッドが遅延なく呼び出されているかどうかに気を配る必要があります。Update() メソッドに渡された GameTime クラスから得られる経過時間を使って、ゲームの進捗を管理してください。
最後に Update() メソッドによって更新された時間は ElapsedGameTime プロパティから取得できます。このプロパティは、最後にゲームが更新されてからの経過時間をゲーム時間で返します。
public TimeSpan ElapsedGameTime { get; set; }
このプロパティが返す時間は、現実の時間ではなくゲーム時間です。そのため、ゲームが停止して Update() メソッドの呼び出しが停止していた場合でも、常に同じ間隔の値を返します。主に、途中の処理を省略できない経過時間を使った遷移処理などに有効です。ゲーム時間に依存するアニメーションなどに使うことができます。
using Microsoft.Xna.Framework; public class TestGame : Game { public static void Main(string[] args) { using (Game game = new TestGame()) game.Run(); } protected override void Update(GameTime gameTime) { Window.Title = "Elapsed GameTime=" + gameTime.ElapsedGameTime; base.Update(gameTime); } }
コード2は、Update() メソッドに渡される GameTime オブジェクトの ElapsedGameTime プロパティが返した TimeSpan の値をタイトルバーに表示します。実行結果を見ると、約 16 ミリ秒間隔で呼び出されていることが確認できます。
デフォルトの設定で起動した場合、常に 16 ミリ秒に固定されて呼び出されます。ゲーム開発の経験がある方であれば、約 16 ミリ秒間隔の呼び出しは馴染みがあるでしょう。16.666... という値は 1000 / 60 に相当するもので、ここから Update() メソッドがゲーム時間の 1 秒間に 60 回呼び出されていることがわかります。