シーン内の状態遷移
実際にゲームを作ってみると、
シーン内の状態遷移が意外に難しいことに気がつきました。
キー入力、タイマー更新、状態遷移をまたがるオブジェクトなど、
グローバルなフラグや変数が増えるにつれ、だんだんぐちゃぐちゃな作りに…。
まあ、簡単にゲームを作るには、
シーン内の状態遷移がないゲームを作るようにした方がいいかもしれませんね…。
でもそんなことを言っては、たいしたゲームを作れなくなってしまうので、
シーン内の状態遷移を考えてみます。
試しにアクションゲームっぽい、シーン遷移を例にします。
エントリポイントは、開始アニメ状態となります。
状態 | 遷移先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的なフローとおさらばできます。
ただ、実装イメージは…。
まだ、脳内にしかないので、ちょっと試してみます。
スミマセン…(´Д`;