状態遷移の柱「簡易シーケンス1」
ゲーム制作で全体像を考えるのは簡単なようでちょっぴり難しい。
プレイの流れがあっちこっちに行って制御しにくいというように(汗
そんなわけで、初心者の悩みの種であるシーケンスの攻略法です。
順番に並んでいること。または、並んでいる順番で処理を行うこと。処理の順番の並びやデータの順番の並びなどもこれに当たる。対義語はランダム。
シーケンスというのは、主に状態の流れのことを指します。
ゲーム制作には、「タイトル画面の状態」「バトル状態」「ゲームオーバーの状態」など様々な場面が存在し、それらをうまく操り動かす必要があります。
そこで、オブジェクト指向の特徴である多様性(ポリモルフィズム)を使う簡単な状態遷移を紹介します。
多様性は、派生したクラスが派生元のポインタを利用できるという話です。
派生元のクラスが持っていた関数を派生したクラスが定義しなおすことで
派生元のポインタにキャストした後、その関数を呼び出すと再定義した関数が呼び出せます。
このように同じポインタ型のオブジェクトでも様々な振る舞いをするから多様性と呼ばれています。
(簡単な説明で分け分からないかも・・・。ごめんなさい。(汗)
今回は以下のように設計しました。
以下はC/C++によるサンプルです。
とっても簡単なので様々な場面で利用できるかと思います。
まずはRootState、全てのシーケンス(状態)の元となるクラスです。
// RootState.h #ifndef _ROOT_STATE_ #define _ROOT_STATE_ namespace Sequence { class RootState { public: RootState(){} virtual ~RootState(){} virtual RootState* Move()=0; virtual void Draw()=0; }; }; #endif // _ROOT_STATE_
次はRootStateから派生するサンプルです。このようにして状態を作ります。
Move関数におけて次の状態を指定することで状態遷移を起こします。(自分自身の場合はthis、他はnewする)
// TitleState.h #ifndef _TITLE_STATE_ #define _TITLE_STATE_ #include "RootState.h" namespace Sequence { class TitleState : public RootState { public: TitleState(){} ~TitleState(){} RootState* Move(); void Draw(); }; }; #endif // _TITLE_STATE_
// TitleState.cpp #include "TitleState.h" using namespace Sequence; //--------------------------------------- // Move //--------------------------------------- RootState* TitleState::Move() { //実行処理 return this; } //--------------------------------------- // Draw //--------------------------------------- void TitleState::Draw() { //描写処理 }
メインループを持つクラスです。
こういった中枢になるクラスはシングルトンにすると便利です。
//Game.h #ifndef _GAME_ #define _GAME_ #ifndef SAFE_DELETE #define SAFE_DELETE(p) if(p){delete p; p=0;} #endif // SAFE_DELETE //宣言だけしとく namespace Sequence { class RootState; }; class Game { public: Game(); ~Game(); void Run(); private: void Draw(); int Move(); int Update(); Sequence::RootState* State; Sequence::RootState* newState; }; #endif // _GAME_
注目すべきはMove関数とUpdate関数です。
どのように状態遷移を起こさせるか確認してみるといいですよ。
#include "Game.h" #include "TitleState.h" //--------------------------------------- // Constructor //--------------------------------------- Game::Game(){ State = new Sequence::TitleState(); } //--------------------------------------- // Destructor //--------------------------------------- Game::~Game(){ SAFE_DELETE(State); } //--------------------------------------- // MainLoop //--------------------------------------- void Game::Run() { while( true ) { Move(); Draw(); if( Update() ) break; } } //--------------------------------------- // Draw //--------------------------------------- void Game::Draw() { if( State ) State->Draw(); } //--------------------------------------- // Move //--------------------------------------- int Game::Move() { if( State ) newState = State->Move(); } //--------------------------------------- // Update //--------------------------------------- int Game::Update() { //------------------------------- // 次のステータス //------------------------------- if( newState != State ) { SAFE_DELETE(State); State = newState; } //------------------------------ // 終了条件(次のステータスが無い) //------------------------------ if( !State ) return -1; return 0; }
応用して様々なパターンを作り出せますが、メモリ管理は注意してくださいね。
(速度が要求される場合はあらかじめ対象のクラスをたくさん生成しておくのも手)
ちなみに、Move()とDraw()に分けてあるのはフレームスキップしやすくするためです。
(ここでは実装してないけどね(汗)