ゲーム開発

Godot自作ステートマシン

Godotのステートマシンプラグインを自作したので、godot-statecharts から切り替えている。

背景

godot-statecharts は、すごくよくできていてお世話になった。しかし、リッチすぎて自分のニーズと合わない点もあった。

今、制作中の Greedy Darts は、想定していたより複雑になってきたし、EAなのでリリース後も更新が続く。ステート管理周りで、いつ、追加で機能がほしくなるかわからない。なので、制約がなくて自分が理解できるステートマシンにしておきたかった。

godot-statecharts 使用時の課題

godot-statecharts は、以下の点が合わなかった。

  • 設定がGUIでポチポチ
  • 欲しい関数が微妙にない
  • グラフィカルな状態遷移図がほしい

設定がGUIでポチポチ

godot-statecharts は、1つのステートがNodeになっていて、それにnameやtransitionなどをGodotエディタ上でポチポチと設定していく必要がある。更に、ステートのNodeごとにsignalもポチポチと追加していかないといけない。

godot-statecharts は、いろいろなステートNodeがあるので、それらを使いこなす人はGUIの方が良いのだと思う。でも自分の場合は、使用するステートNodeは限られている。でも、ステートの数自体は多い。なので、Godotエディタ上でのポチポチが煩わしい。設定ファイルなどのコードでやりたいと思っていた。

signalもステート単位だと、設定が煩わしい。ステートは引数で渡すようにして単一signalにまとめたい。

欲しい関数が微妙にない

godot-statecharts は、なぜかはわからないが、現在ステートやヒストリーを取得する
関数がない。

現在ステートがArgの値かどうかをboolで返す関数はあるが、微妙に使いづらい。複数のステートで分岐したい場合、この関数ではmatch文にできないので、if文を分岐の数だけ書く必要があり、可読性が悪い。

ヒストリーがないのも痛い。history stateというのがあるが、自分が思っていたのと違った。遷移元のステートを取得したいケースがあるけど、既存機能ではできなかった。

user側で変数を管理して、遷移の度にstoreしておけば実現できるが、user側でそんなことは意識したくない。漏れの可能性もある。plugin側で管理して隠蔽して欲しい。

グラフィカルな状態遷移図がほしい

状態遷移図は、mermaidで管理しているけど、規模が大きくなってくると見づらくなる。

これをどうにかしたい。データリネージュツールのように、拡大縮小ができて選択した状態と前後の状態だけライトアップしてくれるようにして欲しい。探してみたけど、これをできるツールが見つからなかった。

で、Godotのグラフエディタで生成すればできるのではと思った。なので、設定したステートマシンからグラフィカルな状態遷移図を出力して欲しい。

自作プラグインに組み込んだもの

設定ファイル

ステートマシンの設定はファイルで作るようにした。最初はtypoや表記揺れを防ぐためにenumを使ったgdファイルにしようと思ったけど、enumを辞書のkeyにすると、期待動作にならなかったので断念。jsonファイルでやるようにした。

以下の様なjsonで設定する。

[
  {
    "name": "Top",
    "transitions": ["SettingGame", "Stat", "Credit"],
    "position": [0, 0],
    "root": true
  },
  {
    "name": "Stat",
    "transitions": ["Top"],
    "position": [1, 1]
  },
  {
    "name": "Credit",
    "transitions": ["Top"],
    "position": [1, 2]
  },
  {
    "name": "SettingGame",
    "transitions": ["Top", "SettingScreen", "SettingAudio"],
    "position": [2, 0]
  },
  {
    "name": "SettingScreen",
    "transitions": ["Top", "SettingGame", "SettingAudio"],
    "position": [2, 0]
  },
  {
    "name": "SettingAudio",
    "transitions": ["Top", "SettingGame", "SettingScreen"],
    "position": [2, 1]
  }
]

nameだけ必須で、あとはoptional。positionは、状態遷移図生成時の位置index。本当は自動で位置判定してグラフを作るようにしてこんな変数はなくしたいのだが、大変なのでとりあえず、指定位置に描画するようにした。

exportした変数に、jsonファイルのpathを設定して、_ready時に読み込むようにした。

表記揺れなどの対策は、遷移時などに軽くassertするようにした。

機能

ステートNodeは1種のみ。名前を持つだけのsimpleなNode。

userが使う関数は以下の4つのみ。

  • get_current_state(): Returns the current state.
  • is_current_state(state: String): Returns true if the current state is the specified state.
  • get_history_states(): Returns the history of states.
  • transition_to(state: String): Transitions to the specified state.

signalは以下の3つのみとした。

  • state_exited(state: String): Emitted when a state is exited.
  • state_entered(state: String): Emitted when a state is entered.
  • state_processing(state: String, delta: float): Emitted when a state is processing.

現状、自分の用途としては、これで十分。

グラフィカルな状態遷移図

これは道半ば。全然できていない。おまけ機能みたいなものなので優先度は低い。

とりあえずGraphEditorでGraphNodeを描画してconnectするところまでやってみた。

全然使い物にならない酷い状態。思ったより難しいかもしれない…

From, ToのConnectionを全部描画したのでめちゃくちゃ。各Node間のConnectionは1つにして、色で片方向/双方向をわかるようにすれば、マシになるかも?

ちなみにmermaidだとこうなる。

とりあえず今は困っていないので、すぐに直す予定はない。

実際に使ってみて

今は、簡単な箇所から、徐々に切り替えていっている最中。

動作は問題なさそう。シンプルで結構気に言っている。