シーン内の状態遷移

実際にゲームを作ってみると、
シーン内の状態遷移が意外に難しいことに気がつきました。
 
キー入力、タイマー更新、状態遷移をまたがるオブジェクトなど、
グローバルなフラグや変数が増えるにつれ、だんだんぐちゃぐちゃな作りに…。
 
まあ、簡単にゲームを作るには、
シーン内の状態遷移がないゲームを作るようにした方がいいかもしれませんね…。
 
でもそんなことを言っては、たいしたゲームを作れなくなってしまうので、
シーン内の状態遷移を考えてみます。

試しにアクションゲームっぽい、シーン遷移を例にします。
エントリポイントは、開始アニメ状態となります。

状態 遷移先1(条件) 遷移先2(条件) 遷移先3(条件) 遷移先4(条件)
開始アニメ メイン(アニメ終了) - - -
メイン 必殺技アニメ(必殺技発動) 会話イベント(ボス登場など) ステージクリア(クリア) ゲームオーバー(やられる)
必殺技アニメ メイン(アニメ終了) - - -
会話イベント メイン(会話終了) - - -
ステージクリア メイン(次のステージに進む) エンディング(ゲームクリア) - -
ゲームオーバー 開始(リトライ) 結果画面へ(終了) - -

なぜ、アニメやイベントを別の状態にしているのかというと、

  • キー入力を受け付けるか受け付けないか
  • キー入力による、操作の対象が異なる

という「キー入力の扱い」が異なるからです。
 
ここは人によって考え方が違ってくるかもしれません。
 
それで、これを図にしたのが以下のものです。

 
この場合の実装方法として、メンバ変数に

class CSceneMain
{
private:
	enum __STATE
	{
		READY,
		MAIN,
		CRITICAL_ANI,
		TALK_EVENT,
		CLEAR,
		GAMEOVER,
	};
	__STATE m_state;
};

と定義して、

CSceneMain::Main()
{
	switch(m_state)
	{
	case READY:
		m_state = MAIN;
		break;
	case MAIN:
		if(必殺技発動)
		{
			m_state = CRITICAL_ANI;
		}
		else if(会話イベント開始)
		{
			m_state = TALK_EVENT;
		}
		else if(クリア)
		{
			m_state = CLEAR;
		}
		else if(ゲームオーバー)
		{
			m_state = GAMEOVER;
		}
		break;
	・
	・
	・
	}
}

とやればいいので、
分かりやすいといえば、分かりやすいのですが、
所謂Goto的なフローになってしまいますね。
 
アクションゲームやシューティングゲームでは、
(たぶん)状態遷移は少ない方なのでまだマシなのですが、
二ヶ月ほど前に、RPGの戦闘シーンの状態遷移をこの方法で実装したときには、
状態が20個以上あって、遷移図がぐちゃぐちゃで死にそうでした。
(というか、そのソースコードは捨てました…(´Д`;
 
 
そこで、「スタック」で実装するのがいいかもしれません。
具体的には、図で矢印を向いているものをスタックに積みます。
 
あと、「イベント処理」というテクニックを使います。
それぞれの状態が「処理できるイベント」であれば処理をスタックに積み、
そうでなければ、上位の処理に制御とイベントオブジェクトを返す方法です。
http://d.hatena.ne.jp/kenmo/20050719#p1のコメント欄参照)

状態 処理可能イベント 発生イベント
トップ ゲーム開始・リトライ・開始アニメ終了・ゲーム終了 -
開始アニメ - 開始アニメ終了
メイン 必殺技発動・やられる・ボス登場・クリア 必殺技発動・やられる・ボス登場・クリア
必殺技アニメ - -
会話イベント - -
ステージクリア ゲームクリア 次のステージへ・ゲームクリア
ゲームクリア - ゲーム終了
ゲームオーバー - ゲーム終了

 
図はこんな感じです。

ツリー階層になっているのがポイントですね。
これで、Goto的なフローとおさらばできます。
 
ただ、実装イメージは…。
まだ、脳内にしかないので、ちょっと試してみます。
スミマセン…(´Д`;