ゲームの部品化
再利用可能な機能の分離
簡単なゲームであれば、Game クラス内にすべてのコードを書いて完成させることができるかもしれませんが、大規模なゲーム開発となると、すべての機能を Game クラスに詰め込むのは設計として大きな問題が生じます。組織的にゲームを開発するには、ゲームを倫理的な役割に分解して、部品単位に開発しなければなりません。加えて、個々の部品は互いに依存せず、再利用可能な形に設計する必要があります。こうした作業には、設計能力が十分にある経験豊富な開発者が必要になります。
幸い、XNA Framework では、アプリケーション開発モデルとして、基本的なゲームの部品化と、個々の部品の結合方法を統一する基盤を提供しています。ゲーム部品(コンポーネント)は、Game クラスとは別のクラスとして開発することができ、後に Game クラスに登録してゲームの一部として機能させることができます。
ゲーム部品は Microsoft.Xna.Framework.GameComponent クラスで表されます。このクラスのインスタンスが、ゲーム部品として扱われます。独自の機能を部品化する場合は、このクラスを継承して独自の機能をオーバーライドします。
public class GameComponent : IGameComponent, IUpdateable, IDisposable
このクラスのコンストラクタには、ゲーム部品を利用する Game クラスを渡す必要があります。GameComponent クラスを継承する場合、基底クラスのコンストラクタに Game オブジェクトを渡してください。
public GameComponent (Game game)
コンストラクタに渡した game オブジェクトは、Game プロパティから取得できます。GameComponent クラスを継承したサブクラス内で、自分自身に関連付けられているゲームにアクセスしたい場合は、このプロパティからオブジェクトを取得します。
public Game Game { get; }
GameComponent クラスは、ゲーム中に含まれるサブゲーム的な役割を提供します。Game クラスと同じように、自分自身を初期化するための Initialize() メソッドと Update() メソッドを公開しています。これらのメソッドは、基本的に Game クラスと同じです。
GameComponent クラス Initialize() メソッド
public virtual void Update (GameTime gameTime)
GameComponent クラスを継承し、これらのメソッドをオーバーライドして、ゲーム部品に必要な初期化処理と更新処理を記述します。Update() メソッドは、Game クラスの Update() メソッドと同じように、ゲーム部品の状態をフレーム単位で更新するために呼び出されます。gameTime パラメータには、現在のゲーム時間が渡されます。
ゲーム部品をゲームに登録する
GameComponent クラスの Initialize() メソッドや Update() メソッドは、登録している Game クラスから呼び出されます。よって、作成したゲーム部品を有効にするには、インスタンスを Game クラスに登録しなければなりません。ゲーム部品を登録するには Game クラスの Components プロパティが返すコレクションに追加します。
public GameComponentCollection Components { get; }
このプロパティは、ゲーム部品を管理する Microsoft.Xna.Framework.GameComponentCollection クラスのオブジェクトです。
public sealed class GameComponentCollection : Collection<IGameComponent>
このクラスは、Collection クラスを継承するコレクションです。コレクションには IGameComponent インターフェイスを実装しているオブジェクトであれば、実体に関係なく追加できます。GameComponent クラスは、IGameComponent インターフェイスを実装しているので、GameComponent クラス、またはこれを継承しているクラスのオブジェクトであれば、Add() メソッドからコレクションに追加できます。
Game クラスの Components プロパティから得られるコレクションにゲーム部品を追加できれば、登録したゲーム部品の Initialize() メソッドや Update() メソッドが呼び出されるようになります。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; public class TestComponent : GameComponent { private string initText, updateText; public string Text { get { return initText + "\n" + updateText; } } public TestComponent(Game game) : base(game) { } public override void Initialize() { initText = "Initialized"; base.Initialize(); } public override void Update(GameTime gameTime) { updateText = "Update: " + gameTime.TotalGameTime.ToString(); base.Update(gameTime); } } public class TestGame : Game { public static void Main(string[] args) { using (Game game = new TestGame()) game.Run(); } private GraphicsDeviceManager graphicsDeviceManager; private TestComponent component; private SpriteBatch sprite; private SpriteFont font; public TestGame() { graphicsDeviceManager = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; component = new TestComponent(this); Components.Add(component); } protected override void LoadContent() { sprite = new SpriteBatch(GraphicsDevice); font = Content.Load<SpriteFont>("TestFont"); base.LoadContent(); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.White); sprite.Begin(); sprite.DrawString(font, component.Text, Vector2.Zero, Color.Black); sprite.End(); base.Draw(gameTime); } }
コード1は、GameComponent クラスを継承する TestComponent クラスを作成し、Initialize() メソッドと Update() メソッドをオーバーライドしています。Game クラスを継承する Test クラスでは、TestComponent のインスタンスを生成し、Components プロパティが返すコレクションにゲーム部品として追加しています。
TestComponent クラスは、Initialize() メソッドや Update() メソッドが呼び出されたかどうかを Text プロパティを通じて返します。実行結果を見れば、これらのメソッドが正しく呼び出されていることが確認できます。ゲーム部品のメソッドは、Game クラスのメソッドから呼び出されるため、Game クラスの Initialize() メソッドや Update() メソッドをオーバーライドした場合は、必ず基底クラスのメソッドを呼び出してください。そうしなければ、ゲーム部品のメソッドが呼び出されなくなってしまいます。
public class TestGame : Game { public static void Main(string[] args) { using (Game game = new TestGame()) game.Run(); } public TestGame() { Components.Add(new TestComponent(this)); } protected override void Initialize() { //base.Initialize(); } protected override void Update(GameTime gameTime) { //base.Update(gameTime); } }
上記のように、Game クラスからオーバーライドした Initialize() メソッドや Update() メソッドで基底クラスの呼び出しを行わない場合、Game クラスが提供するゲーム部品に関連した基礎的な機能が失われ、ゲームに登録した GameComponent の Initialize() メソッドや Update() メソッドが呼び出されなくなってしまいます。
有効化と無効化
GameComponent クラスの Update() メソッドは、Game クラスの Update() メソッドから自動的に呼び出されますが、ゲームの状態によっては一時的にゲーム部品を無効化したいと考えることがあるでしょう。何らかの事情で、特定のゲーム部品だけを停止させたり、その後、何らかの条件によって再起動させることができれば便利です。
ゲーム部品の Update() メソッドを呼び出すかどうかは、GameComponent クラスの Enabled プロパティで制御されます。
public bool Enabled { get; set; }
このプロパティの値が true であれば Game クラスの Update() メソッドが呼び出されたとき、GameComponent の Update() メソッドが呼び出されます。false であれば、呼び出されません。一時的に、ゲーム部品の更新を停止したい場合は、Enabled プロパティを false に設定してください。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; public class TestComponent : GameComponent { private Color color; private bool isInc; public Color Color { get { return color; } } public TestComponent(Game game) : base(game) { isInc = true; } public override void Update(GameTime gameTime) { if (isInc) color = new Color((byte)(color.R + 1), (byte)(color.G + 1), (byte)(color.B + 1)); else color = new Color((byte)(color.R - 1), (byte)(color.G - 1), (byte)(color.B - 1)); if (color == Color.White) isInc = false; else if (color == Color.Black) isInc = true; base.Update(gameTime); } } public class TestGame : Game { public static void Main(string[] args) { using (Game game = new TestGame()) game.Run(); } private GraphicsDeviceManager graphicsDeviceManager; private TestComponent component; public TestGame() { graphicsDeviceManager = new GraphicsDeviceManager(this); component = new TestComponent(this); Components.Add(component); } protected override void Update(GameTime gameTime) { GamePadState state = GamePad.GetState(PlayerIndex.One); if (state.Buttons.B == ButtonState.Pressed) component.Enabled = false; else component.Enabled = true; base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(component.Color); Window.Title = "Color=" + component.Color.ToString(); base.Draw(gameTime); } }
コード2は、フレーム毎に少しずつ色が変化するアニメーションを提供する TestComponent を作成しています。このクラスは GameComponent クラスを継承し、ゲーム部品として機能します。Update() めそっどが呼び出されるたびに color フィールドの値を更新し、黒から白へ、白から黒への遷移を繰り返します。色は Color プロパティで公開されているため、Test クラスの Draw() メソッドで、ゲーム画面の背景として利用しています。ゲームを実行すると、フレーム毎に画面の色が変化していることが確認できます。
Test クラスの Update() メソッドでコントローラの状態を調べ、B ボタンが押されていれば登録している TestComponent オブジェクトの Enable プロパティを false に設定します。B ボタンを押している間 TestComponent の更新が行われないことを確認してください。