本記事について
本記事執筆時点でのUEバージョンは5.3.2となります。
現在UEのAI機能について勉強をしているのですが、
色々と調べていく中でBehaviorTreeを使ってAIキャラクターを作る時に
気を付けるべきことを残していこうと思います
記載している内容はすべて独学のものであり、
実際のゲーム開発の現場などではまた違った認識があったりするかもしれませんが、
その辺りは暖かい目で見ていただけると幸いです
(間違っている箇所などありましたら、コメントなどでご指摘いただけると嬉しいです)
BehaviorTreeを使うときに気を付けるべきこと2点
BehaviorTreeを使うときに気を付けるべきこととして、以下の2点が挙げられます
- BehaviorTreeを肥大化させない
- BehaviorTreeの内部で状態管理をしない
BehaviorTreeを肥大化させない
肥大化してしまった際のデメリット
色々とデメリットはあると思いますが、以下の2点が挙げられます
- 可読性が低くなる
- 保守が難しくなる
肥大化の例
例えば以下の画像のようなBehaviorTreeを見てください
(無意味に配置していっただけなので中身はスルーしてください)
これはテストのために作ったものなので適当ですが、
更に各ノードにDecoratorやServiceが付いたり、各ノードの変数状況を見ていったり…
これらの全体像を把握するだけでも相当な時間がかかってしまいます
このBehaviorTreeの生産者ならまだいいかもしれませんが、
それ以外の人が見たらどうでしょうか…?
私だったらいったん犬の画像とかに癒しを求めに行きます
個人開発ならいいかもしれませんが、チーム制作の場では一人一人のモチベーションは
重要になってくるので、見やすく触りやすいものを心がけていきたいところです
肥大化しないようにするには?
簡単なところとしては、機能ごとにBehaviorTreeを分割することが良いかなと思います
前述したように、肥大化してしまうと簡単な調整もしづらくなってしまい、
BehaviorTreeを使いまわすことも難しくなってしまいます
なので、共通する処理の部分は個別でBehaviorTreeを作成し、
「RunBehaviorTree」などでBehaviorTree上から別のBehaviorTreeを呼び出すようにする、
といった形にすればBehaviorTreeをよりコンパクトに出来るかと思います
BehaviorTreeの内部で状態管理をしない
ここに関してはUEのDevCommunityでも言及されている内容になります(※以下リンク先)
Common issues with Behavior Trees and things you should competely avoid.
BehaviorTreeでは別のノードへジャンプが出来ない
BehaviorTreeはRootからの実行順序が決まっており、
基本的に特定のノードから別のノードへジャンプするといったことは出来ません
そのため、状態を変化させて対応する行動を切り替えるといったことをするには向いていません
実行順序については、以下画像の黄色枠のように表示されています
※公式ドキュメントより引用
特定のノードから別のノードへジャンプできない例
例えば、簡単に以下仕様のAIキャラクターを作るとします
- プレイヤーを見つけていない時 ⇒ 適当な場所を歩く
- プレイヤーを見つけている時 ⇒ プレイヤーを追いかける
- 何もせずその場で留まる
上記の内容をBehaviorTreeで作ってみたものが以下画像の内容になります
実行番号 | ノード名 | 詳細 |
---|---|---|
0 | Selector | 繋がれているノードを左から順番にチェックし実行できるものがあれば、それを実行する |
1 | BTDecorator_IsTargetFound | IsTargetFoundというbool変数がFalseになっていればこのノードを実行する |
2 | Sequence | 繋がれているノードを左から順番に処理する |
3 | BTService_FindLocation | 移動できる座標を計算しBlackboardの変数にセットする |
4 | MoveTo | Blackboardの変数から取得した座標へ移動する |
5 | Wait | 設定された時間だけ待つ |
6 | BTTask_MoveToTargetActor | Blackboardの変数から取得したActorを追いかける |
7 | Wait | 設定された時間だけ待つ |
このBehaviorTreeを使い、番号2~5の処理中にプレイヤーを見つけた時、
即座にプレイヤーを追いかける行動に移したいとします
方法としては番号2のSequenceにBlackboardBasedConditionを追加し、
IsTargetFoundの値が変わった時にキャンセルさせるといった仕組みにすれば問題なく動作します
(具体的な設定方法などは割愛します)
では、番号2~5の処理中にプレイヤーを見つけた時、
番号7のWaitのノード処理へ飛ばしたい、となったらどうするでしょうか?
同じように番号2のところにDecoratorを追加してキャンセルさせても、
次の処理は番号6のBTTask_MoveToTargetActorになってしまうため、
番号7のWaitへ直接ジャンプさせるということが出来ません
もちろん、BTTask_MoveToTargetActorにDecoratorを追加するなどして
通らないようにすることは出来ますが番号7の処理を飛ばすことは出来ないため、
必ずチェック処理が走ることになってしまいます
仮に状態管理を実装する場合
では、これらを踏まえてBehaviorTreeで状態管理をするとしたらどうなるでしょうか?
例えば、某ステルスゲームに出てきそうな巡回する警備兵みたいな敵を作ろうとした時、
以下のようなBehaviorTreeを作るとします
(具体的な中身は省略します)
-
敵の状態
- 番号1:プレイヤー未発見状態(適当に移動させる)
- 番号2:警戒状態(銃を構え辺りを見回しながら移動させる)
- 番号3:プレイヤー発見状態(プレイヤーを追いかけ、近づいたら攻撃する)
この敵において、
番号1の「未発見状態」の時に目の前にプレイヤーが現れた場合は
即座に番号3の「発見状態」へ移行させたい、
ということをしたくなったとします
前回のテストと同様に、
番号1にいるところでキャンセル処理を追加して番号3へ向かわせようとしますが、
必ず「警戒状態にするべきか?」というチェック処理は通ることになってしまいます
どの状態へ移行するか分かっているのに、
わざわざ「警戒状態に出来るか?」というチェック処理が入るのは明らかに無駄です
チェック処理が軽いものなら気にならないかもしれませんが、
複雑な計算処理が走るような場合には無視できない内容になってしまうため、
不要であればなるべく走らせないようにしたいところです
改めて状態管理に向かない理由
ちょっと強引な例えになってしまったかもしれませんが、
BehaviorTreeは処理順番が決まっており処理フローを意図的に操作することは難しいため、
状態管理のようなことは向いていません
状態管理のようなことをしたくなったらどうする?
もし状態管理のようなことが必要になったら、
StateTreeとBehaviorTreeを組み合わせて活用するなど考えるべきかもしれません
※公式ドキュメント:StateTreeの概要
最後に
拙い文章になってしまい申し訳ないですが、ここまで見ていただきありがとうございます
AIについては勉強を始めたばかりで、
UEのBehaviorTreeやStateTreeについてこれから更に勉強を進めていこうと思いますので、
間違っている箇所などありましたらコメントにてご指摘を頂けると嬉しいです
参考資料
以下のドキュメントや動画を参考にさせていただきました