アプリケーションモデル その1 ~Gameクラス~
これまで概要的な話をしてきましたが、今回からは実際のプログラミングについての話をしていこうと思います。
今回は以下の動作を実装します。
- ウィンドウの作成
- グラフィクスデバイスの生成
- デバイスロストの管理
- ゲームのメインループ
- 画面を指定の色で塗りつぶす
DirectXやMDXで以上の作業をするのには、かなりの量のコードを書く必要があり、特にデバイスロストの管理は複雑で、ウィンドウのリサイズ時はもちろん、マルチモニタでのモニタ間のウィンドウ移動、フルスクリーンへの切り替え、スクリーンセーバーが立ち上がったとき、PCがスリープ状態に入ったときなどに対応するには、少なくとも1,000行以上のプログラムを書かないといけません。
DirectXでゲームを作りたい!と思った人が、いざプログラムを始めようとすると、いきなり最初からこんな難関を乗り越えないといけないのは大きな障害以外のなにものでもありません。確かにプロジェクトテンプレートを使えば以上のことはコーディングなしで実現できますが、やはり大量のコードを管理するというのは面倒なことだし、ゲーム製作自体には関係のないことです。
XNAでは、これらの問題に加えて、PC/Xbox360の両プラットフォームでのゲーム開発を容易にするために、アプリケーションモデルがあります。XNAのアプリケーションモデルでは、今回の実装を実現するのに100行程度のコードで、テンプレートも用意されているので実際にコーディングする必要はありません。
以下のコードはXNA Game Studio Express上から、ファイル/新しいプロジェクトを選んだ後にWindows GameかXbox 360 Gameを選ぶことで自動的に生成されるコードです。実際には英語のコメントですが、ここでは翻訳されています。PCとXbox360のコードは完全に同じものです。
/// <summary>
/// ゲームのメインクラス
/// </summary>
public class SampleGame01 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
ContentManager content;
public SampleGame01()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
/// <summary>
/// ゲームの実行する前に初期化必要なものを処理する。
/// ここで必要なサービスの検索や、非グラフィクス系のコンテントの読み込みをする。
/// base.Initializeを呼ぶことで登録されたコンポーネントの初期化する。
/// </summary>
protected override void Initialize()
{
// TODO: ここに初期化用コードを追加
base.Initialize();
}
/// <summary>
/// グラフィクスコンテントの読み込み。loadAllContentがtrueの場合、
/// ResourceManagementModeがAutomatic、Manualの両方のコンテントを読み込み、
/// falseの場合、ResourceManagementMode.Manualのコンテントを読み込む。
/// </summary>
/// <param name="loadAllContent">読み込むコンテントの種類</param>
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
// TODO: ResourceManagementMode.Automaticのコンテントを読み込む
}
// TODO: ResourceManagementMode.Manualのコンテントを読み込む
}
/// <summary>
/// グラフィクスコンテントの廃棄。unloadAllContentがtrueの場合、
/// ResourceManagementModeがAutomatic、Manualの両方のコンテントを
/// 破棄する必要があり、falseの場合、ResourceManagementMode.Manualの
/// コンテントを破棄する。
/// デバイスリセット時にManualコンテントはGraphicsDeviceによって
/// 自動的にDisposeされる。
/// </summary>
/// <param name="unloadAllContent">破棄するコンテントの種類</param>
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
content.Unload();
}
}
/// <summary>
/// ゲームの更新(衝突判定、入力処理、オーディオの再生など)
/// </summary>
/// <param name="gameTime">タイミング値のスナップショット</param>
protected override void Update(GameTime gameTime)
{
// Xbox360とWindowsの既定ゲーム終了処理
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: ここにゲーム更新のロジックを追加する
// 登録されたGameComponentを更新する
base.Update(gameTime);
}
/// <summary>
/// ゲームの描画。必要な時に呼び出される。
/// </summary>
/// <param name="gameTime">タイミング値のスナップショット</param>
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: ここに描画用のコードを追加する
// 登録されたDrawableGameComponentを描画する
base.Draw(gameTime);
// TODO: ここにも描画用のコードを追加したりもする(ポストプロセスとか)
}
}
アプリケーションモデルの核となるのはGameクラスで、このクラスから派生したクラスを定義することで基本的なゲームの初期化やメインループといったものを実現できます。
以上のコードをまとめると、以下の3種類のメソッドが重要な役割を果たします。
- 非グラフィクス部分の初期化を担当するInitialize
- グラフィクス部分のコンテントの初期化や破棄を担当するLoadGraphicsContentとUnloadGraphicsContent
- ゲームメインループを担当するUpdateとDraw
これら3種類の関数内にコードを追加するのが、XNA上でのゲームプログラミングの第一歩になります。
Initializeメソッド
このメソッド内ではゲームコンフィグなどといった非グラフィクスのデータの読み込みや、ゲームの部品になるゲームコンポーネント(後の投稿で説明します)の登録などをします。
LoadGraphicsContentメソッド
このメソッドは、グラフィクスデバイスが作られたり、グラフィクスデバイスのリセットやロストの後にGameクラスから呼ばれます。ここでは、グラフィクス関連のコンテントなどを読み込み処理をするのが適しています。グラフィクスコンテントにはデバイスリセット後に自動的にその内容を元に戻してくれるResourceManagementMode.Automaticと、デバイスリセット後には内容が消えてしまうResourceManagementMode.Manialの2種類があります。殆どの場合はAutomaticのグラフィクスコンテントを使うだけで済みます。
UnloadGraphicsContentメソッド
このメソッドはデバイスリセットやデバイスロストの前にGameクラスから呼ばれます。XNAの全てのグラフィクスリソースはグラフィクスデバイスのリセットやロスト時に自動的にDisposeされるので、ゲーム内で不必要になった時以外に明示的に呼ぶ必要がありません。ですから、通常はなにもする必要がありませんが、例えばLoadGraphicsContentメソッド内で生成したグラフィクスリソースをリストに追加するといった処理をしている場合は、UnloadGraphicsContentメソッド内でリストから削除する処理をするのに適しています。
Updateメソッド
このメソッド内には非グラフィクスのゲームロジックを更新するコードを記述します。既定では、Gameクラスから16.6ms毎、つまり秒間60回呼ばれるようになっています。この更新の間隔はGame.TargetElapsedTimeを変更することで変えることができます。またGame.IsFixedTimeStepの値を既定値のtrueからfalseに変えることによって、GameクラスがTargetElapsedTimeの値を無視ししてUpdate、Drawメソッドを常に交互に呼ぶように振る舞いを変えることができます。ただし、この場合はUpdateメソッドに渡されるgameTimeの内容は変化します。
Drawメソッド
このメソッド内では各フレームでの画面を描画するための処理をします。このメソッドはGameクラスから呼び出されます。
以上がアプリケーションモデルの核であるGameクラスについての簡単な説明です。アプリケーションモデルには、他にGameComponentというのがありますが、これは画面に何かを表示させてからの方が理解しやすいと思うので、次回はテクスチャの読み込みとSpriteBatchでの描画をやりたいと思います。