階層型ステートマシンによるステートデザインパターン

新卒のりやさんです。

こいつブログばっか書いてんな、もしかして暇なのか? とか思わないでください。

今回は、ゲームプログラミングの中でもとても重要なデザインパターン、ステートデザインパターンについて紹介しようかと思います。

通常、クラスというものは「属性」と「振る舞い」の二種類の要素を兼ね備えています。
一般に、データとインターフェースと呼ばれるものですね。

クラスは自身の状態を保持し、きっとカプセル化されたデータに応じて要求された操作に対し適切な振る舞いをすることでしょう。
そのようなコードは列挙体とswitch-caseで簡単に表現できます。

例として、村によくいる住人のオブジェクトを考えます。
住人は、走ったり、歩いたり、或いは居眠りをする挙動を考えます。

この程度のコードならまあ、これでいいんでしょうが、あまりにも拡張性に欠けます。
状態(State)は今3つだけですが、当然追加が発生することもありますし、遷移の順番も変わるかもしれません。

さらに、状態というものはネストすることがあります。例えば、「機嫌が良い/悪い」という状態が加わったらどうでしょう。「歩いている」と「機嫌が良い」は同時に発生しうる状態であり、両方とも別変数で管理し、switch-caseをネストさせて条件分岐させなくてはなりません。

そんなswitch-caseのコードのメンテナンスをしていると、とんでもないことになります(なりました)。

具体的にどうするべきかというと、「状態」というものをクラスにしカプセル化しましょう。
NPCクラス自身が次の状態は何か、今どうするべきかを判断する構造は良くないです。

NPCクラスが呼び出すのはStateのupdateだけ。
あとは勝手に状態遷移をやってくれそうな感じです。

ステートデザインパターンとしてはまだまだ実装すべき項目がありますが、コンセプトとしてはこの方針で問題なさそうです。

私が作成するステートマシンの基本要件は以下。

 

ステートマシンクラス

      1. ステートマシンは必ず「現在のステート」を持つ

2. ステートマシンは階層構造になっており、ステートマシンには親ステートマシンや子ステートマシンが存在する場合がある

3. ステートマシンは特定の間隔(ゲームであるならば、毎フレームが望ましい)でステートに次の遷移先ステートを問い合わせ、状態が更新される

4. 遷移先ステートが同一である場合、遷移は行わないと定義する(本来ステートマシンの元となる有限オートマトンにおいては、この操作は「自己遷移」と呼ばれるものだが、省略する)

5. ステートの遷移が行われた場合、遷移前のステートのイグジット処理と、遷移後のステートのエントリー処理を実行する

6. ステートマシンはイベントの通知を受けた時、現在のステートに処理すべきかどうか問い合わせる。この操作は、イベント処理するステートが見つかるまで子ステートマシンより再帰的に行われる

ステートクラス

      1. ステートは「次のステート」を持つ

2. ステートは「イベント」と「アクション/トランジション」のマップを持つ。ステートマシンより問い合わせが来た時、該当するイベントアクションが存在する場合、実行する

3. アクションは関数の実行、トランジションは遷移の発生である。ただし、拡張性のため、トランジションにはファクトリデザインパターンを採用する

 

そんなこんなで、実装したステートマシンが以下.
(業務のために本格的に作成したものですので、少し大規模になっています)

ステートマシンはupdate関数によってステートの状態を毎回チェックしています。
返ってきたステートが同一ではない場合、ステートの遷移を行います。
その際、ステートのentry関数とexit関数を実行します。

また、dispatchEvent関数によってイベントの処理を行います。
子ステートマシンに先に問い合わせた後、イベントが処理されるまで再帰的に実行されます。

また、メモリの安全性の考慮し、スマートポインタを使用します。

ステートクラスはアクションマップとトランジションマップを持ち、該当するイベントをprocessEvent関数によって実行します。
_nextメンバは次の遷移先ステートを表しており、update関数によって次のステートの問い合わせを受けた場合、_nextが存在するなら_nextを、存在しないならば自身を返します。

こんな感じで、実際にNPCが歩くだけのプログラムを作ってみました。
リポジトリを置いとくので、ステートマシンの使い方の参考がてらどうぞ!
cocos2dx v3.11が必要です。

https://github.com/Riyaaaaa/StateMachineSample

実行結果

ZjItNIl46X

こいつ…動くぞ!

素材提供者 直江ジャスティス様
(https://twitter.com/justicenaoe)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です