今回は、ゲームの状態管理をクラス化する方法をメモります。
今回は設計として、3種類のクラスを作ります:
– 全ての状態(これからはシーンと書きます)の基礎となるクラス
– 基礎クラスを継承した各シーンのクラス(例:メニューシーンクラス, ローディングシーン)
– 全てのシーンクラスをを管理するクラス
最初に各シーンの基礎となるクラスを作ります。まず、各シーンに必要となるであろうメンバーを宣言し、コンストラクタで受け取ります。
abstract class GameState { GameStateManager manager; // このクラスを保存しているクラス ContentManager content; GraphicsDeviceManager graphics; Game game; SpriteBatch spriteBatch; public GameState(Game game, GraphicsDeviceManager graphics, GameStateManager owner) { this.game = game; this.graphics = graphics; this.content = game.Content; this.spriteBatch = new SpriteBatch(graphics.GraphicsDevice); this.manager = owner; }
各シーンごとにこれらのメンバーを保存することによって、Game1に近い形で使えるようになります。
次に、各メンバーのアクセサを作ります。Getだけにしているのは、継承するクラスや、外部から変更できなくするためです。
public Game Game { get { return game; } } public GameStateManager Manager { get { return manager; } } public GraphicsDevice GraphicsDevice { get { return graphics.GraphicsDevice; } } public GraphicsDeviceManager GraphicsManager { get { return graphics; } } public ContentManager Content { get { return content; } } public SpriteBatch SpriteBatch { get { return spriteBatch; } }
最後に、更新と描画の基礎となるメソッドを作ります。このクラスは、上記でabstractクラスとして宣言されており、abstractメソッドを宣言できます。abstractと記されているメソッドは継承したクラスが必ずオーバーライドしなくてはいけません(していない場合は、エラーになります)。これにより、各シーンは更新と、描画のメソッドを持つことを保障されます。
// 継承するクラスはこのメソッドをオーバーライドしなければいけない public abstract void Update(GameTime gameTime); public abstract void Draw(GameTime gameTime);
ちなみにabstractクラスは、インターフェイス同様インスタンスを作ることが出来ません(継承したクラスのインスタンスは作れます)。インターフェイスとの違いは、メソッドを宣言だけでなくコンストラクタやアクセサを含めたメソッドを実装を出来る事です。なので、状況によっては、GameStateクラス内に何かメソッド足すことも出来ます。
基礎クラスが出来た後は、各シーンを管理するクラスを作ります。まず、前回同様各シーンを表すenumを宣言します。
enum GameStates { Menu, Playing, Paused, Shop, Transition, }
次に、管理クラスの宣言部分を書きます。今回、各シーンを保存するのにDictionaryクラスを使用します(Dictionaryクラスについてはこちらをご参照ください)。List<>とかのクラスでなくDictionaryを使う理由は、同じ値を持つkey(今回はGameStatesのenum)を二つ保存できないからと、enumに限らず様々なクラスを中身を得る時に使えるからです。例を挙げると、Dictionary<string, GameState>と宣言すると、”Menu”などの文字列で管理できます(中身を参照する場合はm_states[GameStates.Menu] の代わりに、m_states[”Menu”]となります)。
class GameStateManager { Dictionary<GameStates, GameState> m_states; GameStates m_currentState; public GameStateManager() { m_states = new Dictionary<GameStates, GameState>(); m_currentState = GameStates.Menu; }
m_currentStateは現在のシーンを表し、そのシーンだけ更新と描画をします。次に、シーンを足す、消す、切り替えるをする為のメソッドを作ります。
public void AddState(GameState toAdd, GameStates type) { m_states.Add(type, toAdd); } public bool RemoveState(GameStates toRemove) { return m_states.Remove(toRemove); } public void ChangeState(GameStates newState) { m_currentState = newState; }
後は更新と描画処理です。それぞれのメソッドは、現在設定されているシーンがあるかを確かめ、もしDictionary内に有ればそのシーンを処理します。
public void Update(GameTime gameTime) { GameState state; //現在のstateがある場合、保存されているGameStateへの参照を得る if (m_states.TryGetValue(m_currentState, out state)) { // 現在のシーンを更新 state.Update(gameTime); } }
描画処理も同じ様に実装できます。
これで基礎となる部分は実装できたので後は実際に各シーンのクラスを作るのと、Game1内でそれらを動かすだけです。各シーンのクラスは上でも書いたように、GameStateクラスを継承します。メニューのシーンクラスの実装例は以下です。
class MenuState : GameState { Menu menu; SpriteFont font; public MenuState(Game game, GraphicsDeviceManager graphics, GameStateManager owner) : base(game, graphics, owner) { // ... 初期化処理 } public override void Update(GameTime gameTime) { // ..メニュー画面の更新処理 } public override void Draw(GameTime gameTime) { // ..メニュー画面の描画処理 } }
後は、Game1内でGameStateManagerを使うだけです。
GameStateManager manager; protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); // 新しいシーンを足す manager = new GameStateManager(); manager.AddState(new MenuState(this, graphics, manager), GameStates.Menu); } protected override void Update(GameTime gameTime) { manager.Update(gameTime); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); manager.Draw(gameTime); base.Draw(gameTime); }
以上です。これはあくまで実装例なので、色々と自分の使いやすいようにいじってみてください(特にシーンの切り替え部分は個人差が出ますので、使いやすいようにしてください)。
次回は、今回作ったクラスを色々なゲームに使いまわし出来る様に変更する事を書く予定ですが、それ以外にも実装方法自体を変えることも出来るのでそのへんも使いやすいように変更してください(例を挙げると、AppHubのサンプルはDictionaryではなく、stackを使用して管理しています)。
[…] 今回は、結構前のメモで作った状態管理用のクラスを、どんなゲームでも使いまわし出来るように変更します。 […]
この方法を行ったことがあるのですが、
キー入力によってメニューを選択する場合はどのようにしたらよろしいでしょうか?
選択はタイトル画面だけなのでタイトルのクラスでやるということはわかりますが・・・
手前味噌ですが、メニューの作り方は他のXNAメモ ( http://bit.ly/VxelhY ) で紹介しています。
組み合わせとしては、タイトル等のクラスでメニューを実装し、メニューの戻り値により違うシーンに切り替える、等方法は色々あります。