最初に
どうも、ろっさむです。
今回はUE4でパトロールを行う敵AIを作る手法についてまとめようと思います。
本記事では Behaviour Tree 関連や AIPerceptionに関する詳細な説明は省きますのでご了承下さい。
興味がある方は以下の記事を参照してください。
【UE4】味方AIの作り方!AIとは何かを学びながら、ブループリントで味方キャラクターを実装しよう
作り方
BTとBBの作成
まずは敵用のBTとBBを作成してください。
BT_Enemyの方ではBB_Enemyを設定しておいてください。
今回、BB内で必要となる情報はPlayerのActor情報と、移動地点のVector情報です。
予め設定しておきましょう。
New Keyを押下し、Key TypeをObject、Base Classを Actor にしてPlayerActorという名前のKeyを作成してください。
また、同様にNew Keyを押下し、Key TypeをVectorにしてRoutePointという名前のKeyを作成してください。
これでBBの設定は終わりです。
次にBTの設定を行いましょう。
敵AIのBTの土台を構築
今回のAIでは敵がプレイヤーを発見しているかどうかで行動パターンが異なってきます。
ですので最初のコンポジットノードは Selectorに設定しておきましょう。
ノード名をわかりやすい任意の名前に変更しておいてください。
まずはパトロールを行うパターンを作っていきたいと思います。
コンポジットノードの Sequenceを追加して、Selectorから繋いでください。
パトロールを行っているということは、まだプレイヤーは未発見状態です。
ですので、BBの PlayerActorには何もSetされていないということになります。
デコレーターノードを使用してプレイヤーが未発見の状態の時のみ、この Sequenceノードを通るようにします。
このSequenceでは、敵は設定されている RoutePointを移動することになります。このRoutePointは後程設定するように処理を作成します。MoveToノードを追加して、Blackboard KeyをRoutePointに設定しておいてください。また、 Acceptable Radiusもできるだけ小さい値にしておいてください。
設定後はSequenceに繋いでください。
次に、プレイヤーを発見した際に、プレイヤーを追従するパターンを作ります。
新しくSequenceを追加してわかりやすい任意の名前を設定した後、Selectorに繋いでください。
こちらのパターンは単純にプレイヤーを追うだけなので、MoveToノードを追加して Blackboard Keyを PlayerActorに設定するだけです。Acceptable Radiusは任意の値に設定してください。
設定が完了後、Sequenceノードに繋いでください。
これで一旦土台は完成です。
使用している敵AIControllerのBeginPlayにてBTを走らせる処理を入れておきましょう。またここでBBの変数を作成し、BBの参照を持っておいてください。
敵に視覚情報を追加する
敵のAIController用BPを開いて、Add Componentから AIPerceptionを追加してください。
追加したら、AIPerceptionのDetailsを確認し、Scenes Configの+ボタンを押下してAI Sight configを追加してください。
これで視覚の機能が備わったことになります。
Sight Radiusは敵を中心とする視覚範囲内の半径にプレイヤーが入ってきたら検知するために使用します。
Lose Sight Radiusはその逆で、この半径を超えると見失ったことになります。
この二つは任意の値に設定しておいてください。
また、Detection by Affiliationは全てにチェックマークをつけてください。プレイヤーかどうかの判断は今回はキャストで行います。
設定ができたら、OnPerceptionUpdatedとOnTargetPerceptionUpdatedの二つのEventを追加します。前者はプレイヤーを発見した際に使用し、後者はプレイヤーを見失った際に使用します。
まずはOnPerceptionUpdatedを作っていきましょう。
「On Perception Updated」からは発見したアクター群の配列が出力されているため、一つ一つPlayerのBPであるかどうかを確認していく必要があります。ただし、Playerを見つけることができたらその後の処理は行う必要がないため、途中でループ処理を中断できる「ForEachLoopWithBreak」を呼び出します。
"Array Element"として出力されているActorがどのように発見されたか知ることができる「Get Actors Perception」という関数があります。この関数にActorを渡し、ターゲットとして自身のコンポーネントであるAIPerceptionを繋げます。
出力された"info"というデータを分解し、"Last Sensed Stimuli"という配列の0番目(つまり視覚)を取り出します。
これを更に分解し、要素の最後に存在している"Successfully Sensed"(感知が成功しているか)をブランチに繋ぎ成功していれば、次はプレイヤーを登録する処理に移っていきます。
感知されたActorがプレイヤーBPであれば、ブラックボードのキー「PlayerActor」に設定を行います。

設定後は、もう検索ループを掛ける必要がないので、ノードを先ほどの「ForEachLoopWithBreak」にある"Break"へと繋ぎます。
次に、プレイヤーを見失った場合の処理を作成しましょう。
OnTargetPerceptionUpdatedの実装に移ります。
こちらでは出力されている Simulus Successfully Sensedがfalseであれば、BBからPlayerActorの参照を外すという処理だけを行っています。
これでAIController側での処理は完了です。
Splineを使用してパトロールルートを設定する。
Spline Componentを使用することでレベル上に曲線や直線を設定して、今回のAIのようなパトロールルートとして扱うことができます(もちろんそれ以外にもレベル作成周りとかでバリバリ使えます)。
まずはActorを継承したBPを作成して BP_EnemyPatrolRouteと名付けます。
ダブルクリックでBPを開き、Add Componentから Splineを選択します。
追加できたらレベル上に BP_EnemyPatrolRouteを配置します。配置すると、ぴょろっと点と線が表示されているかと思います。
この点が後々パトロールポイントとなります。点は移動させたり回転させることができます。
点を増やしていくことでルートを作成していきます。
ちなみにルート作成するのにあたって、点を右クリックすると表示される Spline Generation Panelが役立つかもしれません。
実際に使ってみるとよくわかるのですが、円や曲線、四角形などをぱっと簡単に作成してくれます。
また点の数や長さ、点の追加開始位置なども設定が可能です。
他にも点をもし上下で動かしてしまった場合などに、点を選択して右クリックするとFloorへスナッピングなどできます。
点をもりもり増やしてパトロール経路を作成しましょう....
こんな感じにしてみました。
次の段階に進みましょう。
敵BPに経路用のアクターを設定
パトロールの経路を作成した後は、敵側でそのパトロール経路を取得できるようにしましょう。
任意の敵BPを開いて、変数を追加してください。
名前をRouteActorにして、型を先ほどパトロール経路作成用のActor、BP_EnemyPatrolRouteに設定します。
また、レベル上に敵BPを配置した際にエディタ側から設定ができるように Instance Editableにチェックをつけておいてください。
設定ができたらコンパイルして、レベル上に敵BPをパトロール経路近くに配置してください。
配置したら Detailsから先ほどの変数RouteActorを検索し、レベル上に設置しているBP_EnemyPatrolRouteを設定してください。

これでレベル上での下準備は終わりです。
BT上でのサービスノードでパトロールポイントを更新する
先ほど作成したBT_Enemyを開いてください。
開いたら、上部メニューのNew Serviceを押下してBTService_BlueprintBaseを継承したサービスノードを新規で作ってください。
名前をBTS_UpdatePatrolPointとしておきます。
BTS_UpdatePatrolPointを開いてください。
FunctionsのoverrideからEvent Receive Tick AIというサービスノード自身がアクティブ状態の際のTickと、Event Receive Activation AIというサービスノードがアクティブになった際に呼び出されるメソッドを選択してください。
次に今回必要な変数を定義しておきます。
Integer型のCurrentPatrolPointという名前の変数を作成してください。
それから、BBの方で定義しているRoutePointに値を設定するために、BT側からKeyを登録するためのBlackboard Key型のRoutePointKeyという変数も作成してください。
ではまず、Event Receive Activation AIにて初期化処理から実装していきます。
ただ今回は初期化時にパトロールポイントをランダムに設定するための処理をしています。もし不必要な場合はこのイベント自体なくても大丈夫です。消しちゃってください。
まずは、Controlled Pawnから敵BPにキャストを行います。成功したらパトロール経路用に設定したRouteActorからSplineコンポーネントをgetしてください。getしたら、そのSplineで設定されている点の数が何個あるかを取得できるGetNumberOfSplinePointsを追加します。
この値をMAX値としてランダムなint型を生成するRandomIntegerを繋げ、出力された数値をCurrentPatrolPointに設定します。
これで初期化処理は完了です。
次はサービス自体のTick処理です。
初期化処理時と同様にControlled Pawnから敵BPにキャストを行います。成功したらパトロール経路用に設定したRouteActorからSplineコンポーネントをgetしてください。getしたら、Splintで設定した何番目の点の位置を取得するかというGetLocationSplinePointを追加してください。ここでのPointIndexにはCurrentPatrolPointを設定します。またCoordinateSpaceはWorldを設定しておいてください。
取得したVector値をBlackboardのRoutePointに設定します。KeyにはRoutePointKeyを設定してください。
最後に次のパトロールポイントへ進むための準備をします。CurrentPatrolPointをインクリメントして、もしその結果がSpline上での最後のポイントであれば最初のポイントを設定するように0に初期化します。
これでサービスの処理は完了です。
BTに戻って、パトロール中のSequenceノードに追加してください。
仕上げとして、BTにて値が切り替わったら処理を中止するように変更
現在の状態でも動くには動きますが、パトロール場所へ移動中にプレイヤーを見かけても無視して移動が終わってから追いかけようとしたり、プレイヤーを見失うことができません。
これを可能にするために、デコレーターに細工をします。
Notify ObserverをOn Value Changeにし、Observer abortsをBothにしておきます。こうすることで、PlayerActorの値が変わった瞬間に現在実行しているタスクの処理は中断されROOtから判定し直しとなります。Bothは二つのSequenceどちらも中断の対象となるように設定する項目です。
これでBTは完了となります。
結果
実際に実行してみてください。以下のツイートが実装例となります。(Gifでは限界がありました)
Splineを使ってランダムにSplineの点パトロールする敵を作ったー
— ろっさむ (@4_mio_11) November 12, 2020
視界にプレイヤーが入ったら追いかけてくるけど一定距離離れたら(見失ったら)パトロールに戻る pic.twitter.com/0Nd8UlMpKX










































