【UE4】AIのExample Projectを作ってみました（AI編）第2章

#はじめに
[第1章](https://qiita.com/Dv7Pavilion/items/8d92042ced5074563a7c)の続きです。

この章ではNPCであるGuardやBehaviorTree、EQSを紹介＆解説していきます。

#プロジェクトはこちらです！
今回解説するプロジェクトはOneDriveにアップしてあります。バージョンは**4.18.2**です。

[StealthGameExample_Ver1_5.zip](https://1drv.ms/u/s!Au-8FqgREBKZhTC3k-2_VILJrrXl)

#BP_Guard
実際にレベル上にスポーンされるキャラクタでゲーム世界との相互作用する役割を担います。

###BeginPlay
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/937fe33c-7571-4507-5cf2-5fe0294d2888.png" width=80%>

初期化を行うBeginPlayイベントです。
ISetTeamIDでGuardに設定されたTeamIDをAIControllerへと渡しています。渡したTeamIDはAIPerceptionのDetect by Affiliationを動作させるために必要なIDです。
デフォルトでは「1」が入っていますが0～254の範囲であれば好きな数字を入れてしまっても構いません。しかしBP_Playerとは異なる数字を入れてください。TeamIDが異なるキャラクタをGuardは敵と認識します。

###ISetPatrolPointsHolder
ISetPatrolPointsHolderはBP_PatrolPointsHolderの参照を保持しているPatrolPointsHolderをAIControllerへと渡します。
BP_PatrolPointsHolderはGuardの巡回地点をVector配列で保持しGuardからの要求で「次に向かうべき位置」を配列から取り出し返します。


###IChangeState
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/f29726ad-01b7-09b5-ee85-87b5628e8ad1.png" width=80%>

BeginPlayの最後で呼ばれていたIChangeStateです。Guardには物凄く簡易ではありますが状態機械を実装しています。
各状態に応じて**移動速度の変更**と現在どの状態に入っているかをわかりやすくするために**キャラクターメッシュの色を変更**しています。

ChangeWalkSpeedではCharacterMovementのMaxWalkSpeedに変数で渡された新しい速度を設定し、ChangeBodyColorはBeginPlayで作成したキャラクターメッシュのDynamicMaterialInstanceに新しい色情報を設定します。

###IFireWeapon
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/5e3dfe79-16e7-0657-3008-2d984cc17161.png" width=80%>

武器を使用するIFireWeaponはBP_Playerと同様にIFireを通じて武器に命令を出しますがこの時に引数のRandomizeをTRUEに、RandomRadiusに値を設定しています。
詳しくはWeaponの項目で説明しますがRandomizeをTRUEにすると射撃時のブレが発生します。RandomRadiusはブレの大きさを表します。

###ISpotted
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/3ace1d29-e02a-0e03-6060-2630ca9b0a51.png" width=80%>

BP_SecurityCameraから呼ばれたISpottedはここで処理されます。物凄く単純です。Create Widgetでターゲットマーカーウィジェットを生成しAdd To Viewportで画面表示を行っています。
生成したウィジェットの参照を保持しているのはDestroy時にRemove From Parentするためです。

###IDoCover
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/4d2a62f0-9897-fd07-a4ac-34f18b67b48e.png" width=80%>

IDoCoverはカバー処理を行います。この関数を呼び出しているのはBehaviorTreeTaskの**BTT_DoCover**で引数StandCoverに入る値もそちらで求められています。
BTT_DoCoverについては後ほど説明します。

###Dead & Destroyed
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/5f6e5e05-efbc-6626-03cf-fb0429c26275.png" width=80%>

Healthが0になった時に呼ばれるInDeadイベントとワールドから削除される（Destroy）時に呼ばれるDestroyedイベントです。
InDeadでは親クラスのInDeadを呼びラグドール化をします。ラグドール化した後はGuard独自の処理としてBrain ComponentからStop LogicをしてBehaviorTreeの実行を停止しています。

>Stop Logicについては以下のサイトを参考にしました
[UE4でAI(ビヘイビアツリー)を一時停止する方法 Written by WassyPGさん](https://qiita.com/WassyPG/items/944031c819bdf305d702)
[UE4 AIで使う移動と停止のまとめ by Let's Enjoy Unreal Engine](http://unrealengine.hatenablog.com/entry/2017/06/14/005914)

画像のように配布中のプロジェクトではGuardが死亡してもDestroyされることはありません。
Guardには**仲間の死体を見つけた時**の振る舞いも実装されています。是非こういった振る舞いも見てほしかったので今回はわざとDestroyを実行させないようにしています。

***

#AIC_Guard
BP_Guardは世界と相互作用する役割を担いますがAIC_Guardは**相互作用する方法を決定**します。つまりは**意思決定**です。
AIC_Guardはセンサー（ここではAIPerception）を通じて世界の情報を収集します。集めた情報を基にAIC_Guardは適切なオブジェクトに適切な命令を出します。

>AIPerceptionそのものについての説明はここではしません。AIPerceptionは一体何をしてくれるのか？どのような機能があるのか？とAIPerceptionについて不安をお持ちの方は以下のページを参考にしていただければと思います。
[【UE4】味方AIの作り方！AIとは何かを学びながら、ブループリントで味方キャラクターを実装しよう](https://qiita.com/4_mio_11/items/9e8af2ce82ee2a7625b5#aiperception)
[【UE4】AI Perception の紹介と使い方](https://qiita.com/Dv7Pavilion/items/741402f4da609ec595a2)

##OnTargetPerceptionUpdated(AIPerception)

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/cd7570b2-e92e-7f16-6d8e-275734e71915.png" width=80%>

AIPerceptionが何かしらの刺激を受け取るとこのイベントが呼び出され実行されます。
引数Actorには刺激を与えたアクタが渡されます。引数Stimulusは刺激を受けた場所（Stimulus Location）やアクタが見えた・見えなくなった（Successfully Sensed）等の情報が渡されます。

###SwitchToAiStimulis
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/866479b0-e889-ddc4-13de-d4b706b6b133.png" width=80%>

SwitchToAIStimulisは引数Stimulusのクラスに応じた実行ピンに遷移するようなマクロを組んでいます。
調べるクラスはAISense_Sight（視覚から受けた刺激）、AISense_Hearing（聴覚から受けた刺激）の２つです。このプロジェクトではAIPerceptionが受ける刺激は視覚と聴覚だけなのでこのような実装にしました。AIPerceptionにはこれ以外にもいくつかの種類がありますが殆どの場合はこの2つを分類出来れば十分でしょう。


###Hearing（不審な音を聞いた時）

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/1f5c5b59-78aa-19ac-67aa-7490a406647b.png" width=80%>

SwitchToAIStimulisでHearingが実行された場合に呼び出される処理です。Guardは不審な音を聞いた時音が鳴った地点の周囲を探索しGuardにとって敵であるプレイヤーを探します。

IsValidでNullチェックを行い未だプレイヤーを視認していないことを確認します。（探索は敵を未だ視認していない場合のみ実行されます。敵が目の前に居るのに敵を探す必要はありませんから。）
視認していなければGetValueAsObjectで返ってくる値はNULLなのでIsNotValidに処理が流れ敵の探索を実行します。SearchEnemyでは異音が聞こえた地点を中心に周りを探索するので変数CurrentStimulusからStimulus Location（異音が鳴った位置）を引数に渡します。


####SearchEnemy
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/5042f3cf-54e0-2432-319a-3f3facefdebd.png" width=80%>

SearchEnemyという名前が付けられていますがこの関数では敵の探索は行ないません。実際に探索をするのはBehaviorTreeです。
しかしBehaviorTreeで振る舞う際に必要な情報はこの関数で渡し、BehaviorTreeも渡された時点で探索を開始しますので、このような関数名にしました。

関数に入るとSpawnActorFromClassでBP_SearchNodeGenerator（このアクタについては後ほど説明します）が生成されBlackboardに格納されます。
格納する前に既にBlackboard内に別のBP_SearchNodeGeneratorの参照を保存していないかを調べます。参照が保存されていた場合はそれをワールドから削除し、新しく生成したBP_SearchNodeGeneratorと置き換えます。


###Sight（何かを見た時、見失った時）

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/954ec904-228d-2f85-cd22-ba488f5e0124.png" width=60%>

敵が見えた時、見失った時、仲間の死体を見つけた時はこれらの処理が実行されます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/866090c5-f908-6736-56e8-56e3095f03d8.png" width=60%>

Branchでは見えた何かがプレイヤーかをチェックしています。TRUEであればプレイヤーキャラクタの参照をBlackboardのEnemyへ格納します。

次に変数EventTimerHundleをNullチェックしています。変数EventTimerHundleは敵を見失った時に実行されるSetTimerByEventのタイマーで、Nullではない（敵を見失った）場合タイマーを停止＆クリアし、SetTimerByEventで設定したイベントの実行を停止させます。（敵が見えてしまったので「見失った処理」は実行する必要がありません）

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/078a36cf-5654-e742-3f87-85ca695810ce.png" width=60%>

見えた何かがプレイヤーではない場合、それは「自分たちの仲間」と判断されます。仲間と判断されると次に「こいつは死んでいる？」と尋ねます。
尋ねた結果TRUEだった場合Seeing Friend Dead Bodyイベントを実行します。Seeing Friend Dead BodyイベントはHearing時の処理に繋がっており、死体の周囲を探索します。


<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/71ced669-b779-d99a-87ed-9afdefc2b58d.png" width=60%>

AIPerceptionのAISightは「見失った時」もイベントを実行し、そのときStimulusのSuccessfullySensedには**False**が入っています。
それによりBranchはFALSEを通り敵を見失った処理が実行されます。

敵を見失った場合はすぐにBlackboardの情報は更新せず、ある時間待機します。
ある時間というのはAIPerceptionのAI_Sight_Configで設定できる**MaxAge**のことです。（単位は秒）

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/9cf2c3b6-0c66-731d-90d2-e49e37f02727.png" width=30%>

SetTimerByEventの引数Timeには変数CurrentStimulusのExpiration Ageを渡していますが、このExpiration Ageには先程のMax Ageで設定した数値が入っています。
MaxAge秒後にイベントClearSeeingEnemyが実行され、ここで初めてBlackboardの情報が更新されます。BlackboardのEnemyがNullになるとGuardはパトロールに勤しみます。


#EQS（Environment Query System）
EQSは環境の情報を収集し、目的を達成するための**最高の位置**を求める機能です。AIを戦略的に動かすためには欠かせません。

>UE4のEQSについてよく知らない方は公式のページが参考になるかと思います。チュートリアルもありますので是非。
[Environment Query System](https://docs.unrealengine.com/latest/JPN/Engine/AI/EnvironmentQuerySystem/index.html)

##EnvQueryContext（コンテキスト）
EnvQueryContextは**EQSを実行する上で必要な情報を提供するデータ**です。位置を求めるためにEQSはジェネレータを通じてアイテムを生成します。コンテキストはアイテムを生成する位置やアクタをジェネレータへ提供する役割を担います。

###EQC_Player
EQC_Playerはプレイヤーキャラクタを表します。プレイヤーキャラクタへの参照を取得し提供します。
Provide Single Actor関数をオーバーライドして返り値にGet Player Characterで返った値を渡します。

Provide Single Actorは文字通り**一つのアクタの参照**を提供します。他にも複数のアクタを提供する「Provide Actors Set」や位置を表すVectorを提供する「Provide Single Location」、複数の位置を提供する「Provide Locations Set」があります。

Provide Single Actorを使用したのは、このプロジェクトにおいてプレイヤーキャラクターはBP_Playerだけであり、シングルプレイ専用なのでBP_Playerが何人もレベルに存在するわけでもないからです。
もしもマルチプレイに対応していたりプレイヤーのパートナーAI等がいた場合はProvide Actors Setを利用するほうが良いでしょう。

###EQC_CoverObjects
Guardがカバーリング（障害物に身を隠す）するための障害物を提供するコンテキストです。

GetAllActorsWithTagで**CoverObject**とTagを付けられたレベル上にあるオブジェクトを全て取得し渡します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/620ebe1c-b323-f5f4-9fad-cddb87061665.png" width=20%>


###EQC_SearchNodes
敵を探索する際にBP_SearchNodeGeneratorが生成したBP_SearchNodeを提供するコンテキストです。

BP_SearchNodeGeneratorはグリッド状（格子状）にBP_SearchNodeを生成します。GridPointはその生成されたBP_SearchNodeの配列を表しています。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/3560f027-9b66-0323-973f-2cd29492fa67.png" width=70%>


##EQS_SearchEnemy
このEQSでは**敵を探索するのに最も良い位置**を求めます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/4b9064df-b7f6-6333-8b34-def6be3c4035.png" width=50%>

一番上にあるActorsOfClass：以下略は「SearchCenterを中心に半径SearchRadius内にあるSearched Actor Classを探し、そのアクタをアイテムとする」ということをしています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/79791159-598a-3e21-7da8-59c8d46866ee.png" width=30%>

SearchCenterに指定されているEQC_SearchNodesはレベルにグリッド状に配置されているBP_SearchNodeを表しているのでBP_SearchNodeがそのままアイテムとして扱われます。

ActorsOfClass:以下略の下にある青いやつらは**EQSテスト**を表します。ジェネレータが生成したアイテムから最も良い位置を求める**スコアリング**はテストを通して行われます。

###GameplayTagsテスト
UE4にはActor TagやComponent TagがありますがGameplayTagsは少し特殊なタグです。

>GameplayTagそのものについては以下のサイトが参考になるでしょう。
[UE4 Gameplay Tagを使ってゲームプレイ時のタグ管理をより扱いやすくする](http://unrealengine.hatenablog.com/entry/2017/02/21/220000)

GameplayTagsテストではアイテム（BP_SearchNode）に設定されているGameplayTagをチェックし、合致していれば1点、外れていれば0点を与えます。
今回の場合アイテムに**UnVisited（未訪問）**のGameplayTagが付けられていれば、そのアイテムは1点もらえます。

###PathExist:from Querier
PathExistテストは３つのTestModeを備えています。

アイテムに対する経路は存在しているか（到達可能か）を対象とする**Path Exist**
アイテムまでの移動コストを対象とする**Path Cost**
アイテムまでの移動距離（コストとは別です）を対象とする**Path Length**

今回はアイテムまで到達可能かを対象とするPath Existでテストしています。

「from Querier」とありますが、Querierは**このEQSを実行したアクタ**を表します。（今回のプロジェクトではGuard）このテストの場合は「Querierはアイテムまで到達可能か？」をチェックし可能であれば1点与えます。

###Distance : to Querier
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/7ccfc42a-b1a7-4658-a96b-ebaedd843fb9.png" width=30%>

アイテムからDistance toに指定されたコンテキストまでの距離に応じた点数を与えるテストです。点数は１～０までの値が与えられます。

画像ではScoring Equationに**Inverse Linear**が指定されています。Distanceテストはデフォルト設定の場合**遠ければ遠いほどスコアが高い**という具合になっているのですが、Scoring EquationをInverse Linearとすることで、その結果を反転させます。つまり**近ければ近い程スコアが高い**となります。

###Dot 2D : 以下略
このテストは少しむずかしいテストです。Dotと名が付いているので、このテストは内積の結果で点数が付けられます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/56da77fe-204c-c0ae-776e-0c4e76d50969.png" width=30%>

>このテストに関して詳しく知りたい方は以下の記事を読んでいただければと思います。
[【UE4】EQSを使ってInfluence Mapを作った](https://qiita.com/Dv7Pavilion/items/c1fbd53db44be117d38b#dot-2deqc_pursuer-rotation-and-item---eqc_pursuer)

このテストでは「Querierが向いている方向にあるアイテムは高得点」という点数付けをしています。


##EQG_HidingSpot
Guardのカバーポイント（身を隠す位置）を生成するジェネレータです。EQSジェネレータはエンジン側で用意されているもの以外にもEnv Query Generator Blueprint Baseを継承することでカスタムメイドのジェネレータを作成できます。

>実装自体は公式のEnvironment Query System ユーザーガイドを参考にしました。一番下にExample Generatorとして実装例が掲載されています。
（今回のプロジェクトでは少し簡略化しています）
[Environment Query System ユーザーガイド](https://docs.unrealengine.com/latest/JPN/Engine/AI/EnvironmentQuerySystem/UserGuide/index.html)

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/e9a53795-02f0-904e-7f20-b869ed772318.png" width=50%>

引数ContextLocationsから一つ要素を取り出しQuerier Locationとします。その時、-Z方向に少し移動させた値を保存します。
引数から取り出した位置はプレイヤーキャラクタの腰の位置が入っています。この位置にTraceさせると**腰の位置より低い高さの障害物**に衝突せず十分なカバーポイントを作成することが出来なくなってしまうので、引数の位置から少し下の位置を保存するようにしました。

ここでは障害物だけをトレースで検知したいのでプレイヤーキャラクタやAIは検知対象とならないようにGet All Actors Of Classで検索しトレース除外対象として保存します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/0a7a1c5b-ce17-6f79-b73a-7619aa03952c.png" width=50%>

ここでのLineTraceは**円の外側から内側（Querier Location）に向かって**トレースをします。外から内に向かってトレースすることで確実に**プレイヤーキャラクタとの間に障害物がある位置**を取得できます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/25bcf9e9-ab5c-6857-690a-350ed28900a8.png" width=50%>

トレースが衝突すると、衝突した面の法線（Normal）を取得し変数Offset From Wallでスケーリングします。つまり衝突面から離れた位置を求めます。
Normalから求めた位置を衝突した位置（Impact Point）へと加えると衝突位置から変数Offset From Wall分壁から離れた位置が取得出来ます。
取得できた位置はAdd Generated Vectorに渡され、アイテムを生成する位置として処理されます。


##EQS_FindCover

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/acf197af-aa99-5bd1-c45c-974075578774.png" width=40%>

最も良いカバーポイントを求めます。EQS_HidingPointで求められたカバーポイント候補に対してスコアリングをします。

###Trace : to EQC_Player on Weapon
カバーポイント候補からプレイヤーキャラクタに向かってトレースします。通常のTraceテストはトレースがヒットすると1点もらえるのですが、今回の場合はその結果を反転し「トレースが何かにヒットしなければ1点」としています。

結果を反転させることで**プレイヤーへの射線が通る**位置を求めることが出来ます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/4d626868-8c5b-146c-6556-71a65c65a850.png" width=40%>

Traceテストはデフォルトのままだとアイテムの位置からContextへ向かってトレースを行ないますが、今回のような射線が通るか？というテストをする際には良い結果が得られない事がほとんどだと思います。
アイテムの多くは地面に接した位置に生成されます。実際の射線は手元から敵に向かうものですので、そのままトレースしてしまうと腰の高さまでしか無い障害物からでも「ここからは狙えません！」という結果が返ってしまいます。
そこで上の画像の**Item Height Offset**に数値を入力します。この数値によりアイテムの位置がZ方向（上）に移動します。今回は100と入力することで鳩尾・胸くらいの高さまでアイテムをオフセットさせています。

###Dot 2D & Path Exist
EQS_SearchEnemyで説明した内容と同一ですので、省略します。



#BehaviorTree
BehaviorTreeではGuardの振る舞いを実装し、実際にフィールドをパトロールさせたり、攻撃させたりカバーさせたりします。
今までBP_GuardやAIC_Guardで紹介＆解説してきた多くの関数やイベントはBehaviorTreeから実行されます。

>BehaviorTreeの動作が分からない方は以下のスライドを読むのをオススメします。
[【自作ゲーム】 Behavior TreeでAIを作る 【Unity】](https://niconare.nicovideo.jp/watch/kn2055)

>UE4のBehaviorTreeについては公式ページが参考になるでしょう。
[ビヘイビアツリー](https://docs.unrealengine.com/latest/JPN/Engine/AI/BehaviorTrees/index.html)

##BT_Guard
AIC_GuardのBeginPlayで「Run Behavior Tree」を実行していますが、その際に実行するBT Assetとして指定されているのは、このBT_Guardです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/fde2a004-0f0d-c4e3-2d0e-63c632b65540.png" width=60%>

BT_Guardは各状態（戦闘、探索、パトロール）に応じたBehavior Treeを実行させる役割を担います。

各BehaviorTreeへの遷移は単純な比較です。
ツリー右側の「Has No Enemy」は敵を発見したときに値が格納されるキーEnemyをチェックしています。キーEnemyがNULL（敵を発見していない）の場合は以下2つのいずれかを実行します。
Has No Enemyにはオブザーバーを中止に「Both」が設定されており、Guardが敵を見つけると即座にパトロールもしくは敵の探索を中止し攻撃を開始します。

「Has SearchNodeHolder」もHas No Enemyと同様です。この場合はキーSearchNodeHolderをチェックし値が格納されていれば（異音を聞いた・仲間の死体を見つけた）探索を実行します。

##BT_Patrol
パトロールを行うBehaviorTreeです。

これは以下のような順番で実行されます。

1. Change Stateで状態をパトロール状態へと変更します。
2. 「Has Patrol Points Holder」でBP_PatrolPointsHolderの参照を所持しているか確認します。所持していれば下の2つのタスクを実行します。
3. BTT_GetNextPatrolLocationで「次に向かうべき巡回地点」を取得します。
4. Move Toで取得した地点へ移動します。
5. 1から4まで繰り返します。

このBehaviorTreeでは2つのカスタムメイドされたBehavior Tree Taskがあります。

###BTT_ChangeState
BTT_ChangeStateはGuardの状態を変更します。このタスクは各BehaviorTreeで実行されており、BehaviorTreeに応じた状態に遷移します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/8e58e486-6e38-a592-082a-e3107d8c04a1.png" width=50%>

引数ControlledPawnにはこのBehaviorTreeを実行するアクタの参照が入っています。それに対しインターフェースを通じて新しい状態へと変更します。
新しい状態を表すキーNewStateは「インスタンス編集可能」にチェックが入っておりBehaviorTreeエディタで目的の状態を設定することが出来ます。

最後にFinish Executeでこのタスクが「無事に実行された」としています。


###BTT_GetNextPatrolLocation
このタスクはBT_Patrolでのみ呼び出されます。タスク名の通り「次に向かうべき巡回地点」を取得しBlackboardへ適したキーへ格納されます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/696d014c-ebea-a610-2862-a958e7ba9159.png" width=50%>

このタスクには2つのキーが定義されています。

LocationKey : 巡回地点を格納するBlackboardで定義されたキー。Vector型。BehaviorTreeエディタから値を設定する。
PatrolHolderKey : 実行しているGuardが巡回するべき地点全てを保持するBP_PatrolPointsHolderが格納されているキー。BehaviorTreeエディタから値を設定する。

このタスクが実行されるとPatrolHolderKeyからBP_PatrolPointsHolderの参照を取り出します。取り出した参照から関数GetNextPatrolPointを実行し次へ向かう巡回地点を取得します。
取得した地点はLocationKeyへ格納されます。


##BT_SearchEnemy

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/d908663f-72f6-5cf5-9f53-6f133e31d816.png" width=50%>

異音を聞いた時に探索を行うBehaviorTreeです。

以下の順番で実行されます。

1. Change Stateで探索状態へと変更します。
2. Move To Noise Location（Move Toタスクを新しく命名しています）で異音が鳴った地点へ移動します。
3. Run EQS QueryでEQS_SearchEnemyを実行し向かうべき地点を取得します。
4. Rotate（Rotate To Face BBEntryタスクを新しく命名しています）で向かうべき地点へ体を向けます。（これが無くても移動方向へ向くのですが一瞬で振り向いてしまうのでそれを防ぐために使用しています）
5. Move Toで向かうべき地点へ移動します。
6. Run EQS Queryの実行が失敗するまで1から5を繰り返します。失敗するとEnd Searchが実行されます。
7. Release Node Holderで所持しているBP_SearchNodeGeneratorのアクタをDestroyします。
8. Change Stateでパトロール状態へと変更します。

###BTT_ReleaseSearchNodeHolder

<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/d99a7d50-b9d5-cac1-848e-51e241bd4c67.png" width=50%>

このタスクではBlackboardに保存されているSearchNodeGeneratorの参照を取得するのに必要なBlackboardKeySelector定義されていません。
代わりに**Make BlackboardKeySelector**を使用しタスク上でBlackboardKeySelectorを作成しSearchNodeGeneratorの参照を取得しています。

この方法を取ったのは「エディタでの指定忘れ」が発生したためです。BlackboardKeySelectorを定義しインスタンス編集可能とすることでエディタから目的のキーを指定するのですが、自分は度々指定忘れをしてしまいます。処理は問題無いのに指定するキーが間違っているため目的の位置へ移動しない等のバグが発生しました。

自分のせいですが結構イラッと来てしまったので指定忘れを防ぐためにMake BlackboardKeySelectorで直接取得するキー名を指定し取得する方法を取りました。

##BT_ShootEnemy
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/fb4c6811-a5d1-a00a-c760-7cd980d5104c.png" width=50%>

敵を視認すると直ちにこのBehaviorTreeが実行されます。ここではカバーポイントへの移動、カバーリング（しゃがみ）、敵への射撃をしています。

以下の順番で実行されます。

1. Change Stateで戦闘状態へと変更します。
2. Search Node Holderを削除します。（これは探索中に敵を発見した場合の処置です）
3. Weighted Randomは左右いずれかのツリーをそれぞれ50%の確率で実行します。
4. 左のSequenceが実行された時
    1. 強制的に立ち上がります
    2. EQS_FindCoverを実行しカバーポイントを取得します
    3. 敵に銃口を向けながらカバーポイントへ移動します
    4. カバーリングを行ないます
5. 右のSequenceが実行された時
    1. BTD_CloseEnoughで敵との距離が十分離れているかチェックします（近ければ即座に以下のタスクは中止します。）
    2. しばらく待機します
    3. カバーリングでしゃがんでいる場合は立ち上がります
    4. Simple Parallelで以下のタスクを並行して実行します
        1. 敵への射撃を行ないます
        2. 常に敵の方向へ銃口が向くように回転します

###WeightedRandom
このコンポジットノードはエンジンが標準で実装しているノードではなく自作したものです。
WeightedRandomノードの実装や仕組みについては以下のブログを参考にしました。
>[[UE4] Behavior Tree の Composite Node を自作する by rarilog](http://rarihoma.xvs.jp/2017/12/24/1/381)

###BTT_ForceStandUp
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/b255f1e0-2902-b199-de38-fda1bd5db236.png" width=50%>

このタスクではBP_Guardが実装しているイベントI Do Coverを引数Stand CoverにTRUEを入れて実行しています。
I Do Coverは引数がTRUEであればUn Crouchを実行しますので、強制的に立ち上がることになります。


###BTT_MoveTo_LookAt
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/eb6bb5ea-290b-31fa-16c2-40aecc307871.png" width=60%>

BehaviorTreeには標準の機能に「Move Toタスク」があるのですが、BTT_MoveTo_LookAtでは少し改良して指定されたアクタ（もしくは位置）を向きながら移動するようにしています。

イベントグラフは複雑なように見えますが実際にはキーDestinationKeyに格納されている型がObject型かVector型かで分岐させているだけで、やっていることはほとんど同じです。
ここではKeyがVecotr型の場合の処理を見ていきます。

Branchを通ると関数Look At TargetでキーLookAtTargetKeyが持つアクタ（もしくは位置）を見るようにAI Controllerに命令をします。
次にAI Move ToでDestinationKeyが持つ位置に向かって移動をします。

移動が終わると関数ClearFocusを呼び出しFocus状態を解除します。

####関数Look At Target
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/6d803cd8-69ae-efb3-4b0b-28fe5fab23b5.png" width=60%>

LookAtTargetKeyが持つアクタ（もしくは位置）を見るようにするための関数です。
この関数もBTT_MoveTo_LookAt同様にLookAtTargetKeyがObject型かVector型を持っているかで処理を分岐させています。

ここではObject型だった場合の処理（上側）を見ていきます。

Branchを通ると関数SetFocusに引数で渡されたInput Pin（BTT_MoveTo_LookAtのOwner Controllerが渡されます）とLookAtTargetKeyが持つObjectをActorへ変換してNew Focusへと渡します。

関数SetFocusはエンジン側に実装されている関数で引数New Focusに指定されたアクタ方向を向くようにAI Controllerに命令するものです。これは一度呼ばれるとずっと対象となるアクタを向き続けます。
向き続ける動作を停止させるには対となる関数ClearFocusを呼びます。BTT_MoveTo_LookAtでも最後にClearFocusを呼び出しています。

>アクタ方向へAIを向かせるにはSetFocusを使いましたが、位置（Vector型）へAIを向かせるには**関数SetFocalPoint**を使用します。
ちなみにSetFocusの実装では引数で渡されたアクタから位置を取り出し（GetActorLocation）それをSetFocalPointへと渡しています。


###BTT_DoCover
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/4a5a60d6-c574-f104-1fa3-aa59f25a6f2b.png" width=50%>

カバーリングを行うタスクです。

ここでの処理は関数TraceAroundが重要ですのでそちらを解説します。


####関数TraceAround
<img src="https://qiita-image-store.s3.amazonaws.com/0/104467/2d5d2e77-cb91-86f5-ead1-03f69e5fc073.png" width=60%>

関数TraceAroundはEQG_HidingSpotを参考に処理を組みました。

引数Centerにはトレースする際の中心位置が渡されます。
あとの流れはEQG_HidingSpotと同じです。トレースが何かにヒットすれば即座にTRUEを返し処理を抜けます。

この関数はGuardの腰あたりから周囲にトレースをします。そのトレースが何かにぶつかった場合、「あ、目の前には背の高い壁があるな」と判断されGuardは立った状態でのカバーリングをします。（実際は立ちっぱなしなだけ）
トレースが何にもぶつからなかった場合、周囲にはGuard全体をカバー出来るほどの障害物は無く背の低い障害物しかないためGuardはしゃがんだ状態のカバーリングをします。

#[最終章はこちらです](https://qiita.com/Dv7Pavilion/items/507f59cce9c2b4aae2ee)