2009年2月16日月曜日

ステートパターン


ここのテトリスでは、メニュー画面、プレイ画面、ハイスコア画面の管理にデザインパターンの一つであるステートパターンが使われています。ステートパターンはゲームの設計手法としてはよく使われているようですね。

ステートパターンの特徴としては、単一のクラスで状態の変化に応じてif文やswitch文を使ったコーディングをするとコードが徒に長く読みにくくなるのを、窓口役と各状態に応じたクラスに分割し、ポリモーフィズムをうまく使って、コードをすっきりと保守しやすいものにできるということでしょうか。
ここでは、以下のような構成になっています。

CStateManager


現在のステートを保持し、Drawメソッド等、外部(ウィンドウクラス)とのインタフェースとなるメソッドや、ステートを変更するChangeState()が定義されています。

CGameState


ステートのインターフェースを定義するクラスです。コンストラクタで渡されるCStateManagerのインスタンスを保持します。

CMenuState


メニュー画面を管理するクラスです。CGameStateを継承します。

CPlayState


テトリスプレイ用のゲーム画面を管理するクラスです。CGameStateを継承します。

CHighScoreState


ハイスコア画面を管理するクラスです。CGameStateを継承します。

CMenuState、CPlayState、CPlayStateの各クラスは、CGameStateから継承したDrawメソッドを実装しており、CStateManagerのDrawメソッドから委譲される形で呼ばれます。
各ステートへの変更は、メニュー画面でのメニュー選択やゲーム画面でのESCキーの入力に応じて、現在のステートから、繊維先のステートへCStateManagerへ依頼する形で実現されます。このChangeStateメソッドは次のようになっています。

void ChangeState(CGameState* pNewState)
{
if (m_pActiveState)
m_pActiveState->LeaveState();
m_pActiveState = pNewState;
m_pActiveState->EnterState();
}

ここで呼ばれている、LeaveState()とEnterState()もCGameStateで定義されていますが、これもセオリー(よくある)パターンですね。
CGameStateからは次のように呼ばれます。

m_pStateManager->ChangeState(pNewState);

例えばCMenuStateからは次のように呼ばれます。

switch (m_iCurrentSelection)
{
case 0: // 新規ゲーム
if (!m_pCurrentGame)
m_pCurrentGame = CPlayState::GetInstance(m_pStateManager);
m_pCurrentGame->Reset();   // 前回内容をクリアして
ChangeState(m_pCurrentGame); // ゲーム画面へ
break;

case 1: // ゲーム再開
if (m_pCurrentGame && !m_pCurrentGame->IsGameOver())
ChangeState(m_pCurrentGame); // 途中のゲームがあれば再開
break;

case 2: // ハイスコアの表示
ChangeState(CHighScoreState::GetInstance(m_pStateManager));
break;
:

ってぐだぐだ書くより、図で説明した方がわかり易いんですけどね。(^_^;

0 件のコメント: