Godotで3D空間をキー入力によって動き回るプレイヤーキャラクターの動作をステートマシン化します。
ステートマシンというのは有限オートマトンとも呼ばれる、有限個の状態と状態ごとの動作、状態間の遷移機能を持った機構のことです。
ここではプレイヤーキャラクターを待機・移動・ジャンプ・落下の4つの状態に分けてそれぞれの状態で動作の処理を行い、キー入力などの条件で状態間を遷移するようにします。
まずはステートマシンを使わずにCharacterBody3Dのスクリプトの_physics_process()関数内ですべての処理を行っている場合のコードを見てみましょう。
このコードはCharacterBody3D用スクリプトのテンプレートで自動生成されるものです。
CollisionShape3D等を追加したうえで上記をそのままアタッチすればキー操作で3D空間を動き回るキャラクターができますが、ここから機能を増やしていこうとするとif文やネストが増えて煩雑になっていきます。
そこでステートマシン化です。
ステートマシン化には、動かしたいCharacterBody3Dの子としてステートマシンノード、さらにその子として個々の状態をあらわすステートノードを追加する必要がありますが、それらはGodotに標準で搭載されていないので自前で作ります。
まずNodeを継承したStateMachineというステートマシン本体のスクリプトを書きます。
コードは以下です。
_ready()関数で子のステートノードをすべて取得して辞書に登録し、_process()と_physics_process()では現在のステートの処理を実行、ステート切り替えのシグナルが送られてきたらon_child_transitioned()で指定されたステートに切り替えるという機能を持っています。
状態をあらわすステートのスクリプトはNodeを継承したStateを用意して、そこからそれぞれ継承して作ります。
継承元のStateクラスは以下のようなものです。
ここにはシグナルの宣言と空の関数だけを用意します。継承先で書き換えることでそれぞれの処理を実現します。
待機状態のステートクラスのスクリプトは以下です。
キー入力についてはわかりやすい名称のアクションをプロジェクト設定から追加した想定ですので適宜読み替えてください。
ここでは文字通りキー入力等による状態の変化を待機しています。
以下それぞれ移動・ジャンプ・落下のステートのスクリプトです。
移動キーが押されれば移動ステートに、ジャンプキーが押されればジャンプステートに、そしてジャンプの勢いがなくなった後や移動で床から外れた時などには落下ステートに、それぞれ遷移して処理を行います。
これらのスクリプトを作成・保存すると、CharacterBody3Dに子ノードを追加する際の選択肢として現れてくれるのでシーンツリーを以下のような構成にします。
CharacterBody3D
└StateMachine
├IdleState
├RunState
├JumpState
└FallState
それぞれのステートで@exportしたactor変数にはインスペクターから親のCharacterBody3Dを指定しておきましょう。
こうすることで、CharacterBody3D自体のスクリプトは以下のようにできます。
ここまでで最初のコードとほぼ同じ挙動のままステートマシン化できました。厳密には空中での前後左右移動ができなくなっていますが、それもジャンプと落下のステートにそれぞれ処理を追加すれば再現できます。あるいはステートマシンそのものをふたつ用意して前後左右移動系とジャンプ系の状態を分離・併存させることでも再現できるでしょうか。
全体としてはコードの行数もスクリプトファイルの数も増えていますが、可読性の良さはこの状態でも十分わかるかと思います。ここから機能を拡張していくにしてもやりやすさは段違いでしょう。ゲームの規模が大きく、複雑になっていけばいくほど効果を実感できるはずです。
今回はプレイヤーの操作キャラクターでしたが、キー入力ではなく特定の条件下で動作を変える敵キャラクターの実装にこそこのステートマシンは威力を発揮すると思われます。
例えば、プレイヤーとの距離を測って遠くにいるときは追いかけてくる、逆に一定の距離に近付かれたら逃げる、攻撃範囲に入ったら攻撃する、というような具合です。
他にも色々応用ができそうですね。
参考文献
この記事を書くにあたって以下の記事を参考にさせていただきました。参考というか、ステートマシン本体などはコードそのままです。
以上です
ここまでお読みいただきありがとうございました。
よろしければ購読をお願いします。