ここは[第16回UE4ぷちコン]振り返りの子頁です。
概要
alwei先生提供のState Machine Componentを今回も拝借
カスタマイズと使用例
責任を委譲でき拡張容易でバグりにくい。レッツエンジョイ
紹介
とにかく以下の記事をレッツエンジョイ。
ステートマシンを利用する良さについてはStateパターンとかStrategyパターンとかで検索すると詳説が読める。書籍では定番のGame Programming Patternsにも載っている。
UEでの利用シーンで言うと、1つのBlueprintがでかくなりすぎた時、状態ごとに処理や変数をComponentに分割することで、条件分岐の変数管理の複雑さを回避できる。新しい機能を足しやすくなる(様にしやすい)し、新しい機能の編集が他の機能に影響しない(様にしやすい)ので、バグりにくい(はず)…という感じだろうか。(やや弱気)
カスタマイズ
Projectで運用するにあたってカスタマイズした点を紹介する。改善というよりは、単にスタイル的な好みとか、わかりやすさ重視であろう所をわかった風にいじったりしているだけな気もする。おゆるしください…!
Blueprint Interfaceを介さずにMachineはStateの関数を直接呼ぶ様にしてみた。
StateがInterfaceの役割を十分果たしている
MachineはStateありきなので依存してよい
Stateの派生BP作成時(後述)の手間軽減
インタフェース追加
Registered: Machineへの登録時に呼出
PostTick: 全StateMachine持ちActorがTick後に呼出。今回未使用
Hit: On Actor Hit時に呼出。今回未使用
GetName: 名前取得。これだけ実装を持つ。
Name変数追加
MachineにName -> StateのMap型で管理させる為
Map型を使うのはState変更時に総当りさせない為
State遷移用のEvent Dispatcher追加
引数はName(State名を入れる想定)
State自体にはMachineを意識させない為
State検索や遷移処理はMachineに責任があるものとする
State追加処理
Name -> State型のMapに格納
この時にOn State Registered呼出
ついでにState遷移のEvent DispatcherをBind
全State登録関数
ActorのConstruction ScriptかBegin Playで呼ばれる想定
Machine所有Actorに都度Add Stateさせずに済む
動的にStateが増減する場合は従来通りAdd/Removeすればよい
Name -> StateのMap型で保持しているので、遷移時はFind一発でOK。
使用例
プレイヤーとエネミーで利用した。ここではプレイヤーへの適用例を紹介する。
上述のComponentをそのまま使うのではなく、StateもMachineも専用に派生したChild Blueprintを作る。
BP_PlayerStateはStateComponentの派生
BP_PlayerState_* はBP_PlayerStateの派生
BP_PlayerStateの中身。
Player用Blueprint Interfaceの実装を持つ
PlayerはCurrent Stateに処理を委譲するだけで済む
例えば視点操作入力Input Rotの呼出に対して、Plot State(道を引くモード)はカメラを回転させるが、Attack State(攻撃演出モード)は何もしない。
Player用StateはほぼPlayerクラスの参照を欲するので、Machineへの登録時にOwnerをCastして変数として保持
都度のCastを回避できる
最終的にこれぐらいStateが増えた。「これいる?」と思うStateもあるかもしれない。ぶっちゃけいらないのある。後知恵だと不要とか、統合できるとかわかる。だが仕様に「みち」の部分が多い場合は積極的にStateを作っていく事で複雑化を回避できる(テーマ回収アピ)。
Jump State(道を決定後の移動モード)の関数や変数。下記の機能と責任をBP_Playerから委譲されている。
時間経過とともに加速するのでElapsed Secで経過時間を保持
今回の移動で攻撃があるかないかを保持
攻撃時は敵に視点をロックするので座標を保持
攻撃しない時は視点入力を受け付けるので入力受付
こんな細々したのを全部Playerが管理してたらヤバイ。バグる。初期化とか後始末忘れる。でもStateなら大丈夫。OnState Beginとかで自分の責任だけ全うすればいい。これならぼくにもできる。
反省・所感
State名の直打ちもナシにしたい
State追加時にState名入れるの忘れる
名前の変更に弱い
そして考えながら作る時は名前変えがち
Enumで指定できるようにすべき?
Machineはbyte -> StateのMapで保持
Stateは識別子Byteを保持
派生StateでRegister時にEnum To Byteして識別子Byteに保持
State追加時に作業があるが、一度やれば後はバグらない…はず
Playerに対するBlueprint Interfaceが1つしか無かったが、「Stateに処理を委譲するInterface」を分離したほうがいいかも
使う側は意識しなくていいので分けられる…はず
現状、他クラスがGet PawnしてPlayer用に呼び出すInterfaceとごっちゃになっている
例えば"Death"はDeath Stateに遷移するだけであり、各Stateが実装を持つことがない
…んだけど、どの状態でDeathるかによってリアクションを変えたい場合もあるかもしれないからこれでいいのか?
わからん…なんも…
Unreal Quest時もお世話になったが、改めて効果を実感
制作が詰まってくるほど、演出待ちやらなんやら細かい状態が増える
それらをさっと詰め込んでバグらない。最高!
alwei先生、有難うございます…!🙏
偉大なるGoF兄貴達にも感謝…!🙏