Let's Enjoy Unreal Engine

Unreal Engineを使って遊んでみましょう

UE4 AIを制御できるビヘイビアツリーを使ってみる

UE4ではキャラクターなどのAIを作成する仕組みとしてビヘイビアツリーというものが存在しているので、解説してみようと思います。

ビヘイビアツリーというものはAIを作る仕組みとしてメジャーな手法のひとつで行動(振る舞い)をツリー構造上に配置して、流れが複雑になりがちなAIをスッキリとさせることができるようになります。

有名なゲームだとHALOで使われているそうです。

世界に先駆けて「Halo 3」と「SPORE」のAI構造が公開!! 「Three Approaches to Halo-Style Behavior Tree AI」

日本ではステートマシンの方がメジャーなので、あまり使用されている例はみたことがありません。(フロムソフトウェアさんのゲームではよく使われているそうな)

 

今回からUE4.3.1の日本語化されたインターフェースで解説していきます。本来は英語の方がいいのかもしれませんが、こちらが標準で使われるようになると思いますので、今後も日本語インターフェースでいきたいと思います。

UE4でビヘイビアツリーを使う時に必要になる要素

UE4でビヘイビアツリーを扱う時は少し特殊です。本当は公式ドキュメントから引用できればいいのですが、ビヘイビアツリーに関しては英語も含めて、なんと公式なドキュメントが存在していません。なので日本語の解説は本当に皆無だと思いますが、私なりの解釈で解説していきたいと思います。

まず必要となる要素が色々とありますが、以下にまとめます。

  • タスク
  • サービス
  • コンポジット
  • デコレーター
  • ブラックボード

今回、この中で特に重要となるのが、タスク、コンポジット、ブラックボードです。サービスやデコレーターは、タスクやコンポジットを補助するためのツールなので特に最初は気にする必要はありません。今回もここでは解説しません。それでは残りの3つに関して解説しましょう。

AIの行動自体を担当するタスク

タスクはその名の通り、何かしらの処理を実行します。具体的に言えばこの部分にAIが実行する処理が含まれています。このタスクを分割し、再利用性を高めていけば様々な状況に対応するAIが作れるというわけです。

行動のフロー制御を行ない、タスクを繋げるコンポジット

タスクを制御するためにツリーのフロー制御をするためにコンポジットと呼ばれるノードでタスクを繋ぎます。このコンポジットがビヘイビアツリーを作っていると言っても過言ではありません。

値の受け渡しを行うブラックボード

タスク間での値を制御するためにはブラックボードと呼ばれるデータアセットを作ります。この中にブラックボード用の変数を作っていればビヘイビアツリー上ではどのタスクであってもその変数を共有することができます。

実際にビヘイビアツリーを作っていく

早速ビヘイビアツリーを作っていきたいところですが、色々と準備が必要です。まずはビヘイビアツリー自体を使えるようにするためにエディターの設定を変える必要があります。メニューから"編集"→"エディタの環境設定"→"実験的"→"Behavior Tree Editor"にチェックを入れればOKです。

f:id:alwei:20140801180704j:plain

それではコンテンツブラウザーから色々と作っていきます。まずは新規アセットで"未分類"→"ビヘイビアツリー"と選択。名前を適当に決めます。次も"未分類"→"ブラックボード"と選択して、同じく名前を決めます。

次はAIコントローラーを作ります。新規アセットからブループリントを作成し、"AIController"と検索欄に入力し親クラスとして選択します。次にもうひとつブループリントを作成し、今度は"Character"クラスを親にして作成します。

最後にブループリントでビヘイビアツリー用のタスククラスを作成します。同様にブループリントを新規作成し、"BTTask_BlueprintBase"を親にして作成します。今回はタスクを2つ作るので、違う名前で2つ作成しておきます。以下のような具合でアセットが揃えばOKです。

f:id:alwei:20140801181706j:plain

では実際にビヘイビアツリーアセットを開きます。詳細にある"Blackboard Asset"を選択肢して、作成したブラックボードを割り当ててください。

f:id:alwei:20140801182045j:plain

そしてビヘイビアツリーの右上のタブをブラックボードに切り替えます。ここで新規キーというものを作成できるので、"New Key"ボタンからキーを作成します。作成したキーを選択して、Entry Nameを"ValueString"、Key Typeを"String"型にしておきます。

f:id:alwei:20140801182413j:plain

これでビヘイビアツリー内で変数の共有ができるようになります。

次にさっき作ったCharacterクラスを親にしたブループリントを開きます。そしてデフォルトタブの中で"AIController Class"という項目があるので、ここに同じくさきほど作成していたAIControllerクラスを親にしたブループリントを割り当てます。

f:id:alwei:20140801182722j:plain

次にここで割り当てたAIControllerクラスのブループリントを開きます。グラフモードに移行して以下のようなノード構成を作成し、ビヘイビアツリーアセットを割り当てておきます。

f:id:alwei:20140801182954j:plain

これでビヘイビアツリーを実行する準備は整いました。次はビヘイビア部分となるタスクを作成しましょう。

ビヘイビア部分を担当するタスクの作成

では今度は"BTTask_BlueprintBase"を親にしたタスククラスを開きます。グラフモードにして、まずは変数を作りましょう。ここで変数名は必ずブラックボード上で作成したキー名と一致させる必要があります。さきほど入力した"ValueString"で変数を作成して、変数の型を"BlackboardKeySelector"という型にしておきます。

注意しておかないといけないのは、変数の詳細で"編集可能"というフラグにチェックを入れておくことです。これがないとビヘイビアツリーから変数を扱うことができません。

そして通常のブループリントを作成するのと同じようにノードを置いていきます。

f:id:alwei:20140801183947j:plain

イベント Receive Executeがスタートイベントになり、Finish Executeが終了となります。この引数の"Success"フラグはとても重要です。後にも出てくるので覚えておいてください。

とりあえずValue Stringは文字列として扱いたいので"Set Blackboard Value as String"というノードを呼び出して、Valueの中は何でもいいので文字列をいれておいてください。こちらを"TaskA"ということにして、もうひとつ作成していたタスクを"TaskB"ということにしておきましょう。

そして今度はTaskBを開き同様にグラフでノードを作成していきます。同様の手順で変数"Value String"を作成しておき、以下のようなノード構成を作成します。

f:id:alwei:20140801185603j:plain

TaskAで値を設定したValue Stringを取得して、画面上に表示させているだけで特なにもしていません。しかし互いのタスクはブループリント上でも全く別のものなので、本来であれば全く関連性がないものです。それではそろそろラストに入っていきます。

実際にビヘイビアツリーにノードを作っていく

ここまでくるともう一息です。ビヘイビアツリーのノードを作っていきましょう。まず最初の状態ではルートノードしかないと思います。このルートにブラックボードの名前がついていれば正常な状態です。

試しにルートノードの下部分を引っ張ってみましょう。

f:id:alwei:20140801190140j:plain

"Composites"という名前の選択肢が現れました。そうこれが最初に紹介していたコンポジットです。3つでてくると思いますが、メインで使用する"Sequence"と"Selector"のふたつを解説します。

まず"Sequence"は伸びている子ノードを左から右へと順番に評価していきます。子ノードが成功を返すと次は右側に存在する子ノードへと順番に評価していきます。ここで言う成功とはさきほど解説していたタスクにあったノード"Finish Execute"の"Success"フラグです。

Sequenceの場合はノードが失敗した場合、再度ノードの評価に入り、全ての子ノードが成功を返すまでは、Sequenceが親ノードに制御を返すことはありません。流れが全て成功するまでがSequenceということです。

次に"Selector"ですが、子ノードを左から右へと評価していく点ではSequenceと同様です。どこが違うかと言うと、ノードが成功した場合、制御を親ノードに戻すということです。つまり、どこかのノードで成功を返すと全ての子ノード評価が終了して、親ノードの制御になるというところです。

 ノードを最後まで辿るか、途中でやめさせたいか、この違いでSequenceとSelectorを使い分けるといいと思います。"Simple Parallel"はタスク実行中に並列して別のタスクを実行することができますが、ここでは話が長くなるので解説はしません。

話が長くなりすぎて頭がこんがらがってくると思いますので、実際にノードを作成して実行してみましょう。

f:id:alwei:20140801192516j:plain

今回はこのようなツリー構成を作成しました。ルートから左から右へと順番に実行されていきます。忘れておくと危ないのが、少しでもノードを弄ったら保存ボタンを押しておいてください。保存しないと反映されていないので、いくらノードを弄っても変化が起きません。

さて、今回はこのツリーを実行するとまずTaskAが実行されて、終了後に"Wait"という待機タスクを3秒間いれています。これは最初から用意されているタスクなので簡単に使うことができます。他にも色々とタスクが用意されているので調べてみてください。

3秒の待機後、Selectorの評価が終わり、Sequenceノードが成功と判断して、右側のSelectorを実行しにいきます。TaskBでは単純にTaskAでセットした文字列を表示させているだけです。それが終了後にまた3秒間のWaitタスクを挟んで終了します。

終了すると再度ルートからTaskAを実行するというループが一通りの流れです。これでビヘイビアツリーの完成です!最後に忘れてはならないのが、最初に作成していた、Characterクラスのブループリントをなんらかの形でレベル上に配置しておくことです。

他のブループリントから呼び出しても構わないですし、そのままレベル上に配置させておいてもOKです。これで準備ができたら、シミュレートボタンか再生ボタンを押すとビヘイビアツリーをもったキャラクターが動いたことが確認できるはずです!

f:id:alwei:20140801193737j:plain

実際にビヘイビアツリーが動作しているところ。(X)という数字部分が評価されるノードの順番になっています。

本来の形

今回は単純にするために文字列をゲーム上に表示させただけですが、当然キャラクターなので動かすことができます。MoveToタスクなどを使えば簡単に移動もできますし、タスクを自分で量産していけばあらゆる振る舞いを作ることができます。

また今回解説しなかった、サービスやデコレーターはタスクとコンポジットに更に付加条件をつけたり、汎用的な仕組みを提供してくれるものです。ただし、現状ではそのドキュメントが皆無なのでどうやって使うのかは今ひとつ不明瞭です。

しかしビヘイビアツリー自体はとても有用なものだということがわかったと思います。キャラクターAIやそれ以外のAIの仕組みにも使えると思いますので、ぜひ一度使ってみてください!