追いかけてくる敵はできた。次は、撃たれたら物陰へ逃げてほしい。でも、岩を置き直すたびに「隠れる座標」まで手で設定し直すのは大変です。
EQS(Environment Query System) は、周囲の候補を条件で比べ、今の状況に合う場所を選ぶ仕組みです。「プレイヤーから見えない」「そこまで歩ける」「自分から近い」を組み合わせると、地形に合わせて隠れ先を探せます。
この記事でわかること
- EQSが「候補を出す・除外する・点数を付ける」で成り立っていること
- 誰から隠れるかを決めるContextの作り方
- 見えない・歩ける・近いを組み合わせて隠れ先を選ぶ手順
- Testing Pane で、なぜその地点が選ばれたかを見る方法
今回は、候補点を目で確認してから、被弾した敵が隠れ先へ移動する処理へつなぎます。銃がなくても、Nキーでダメージを与えて試せます。
- 候補を作る、除外する、採点する、選ぶ、という4つの段階
- 「誰から見えないか」を決めるContextの作り方
- 点の色と数値を見て、設定を一つずつ確かめる方法
- 隠れ先が見つからない場合も含めたBehavior Treeへの組み込み
「良い場所」の条件を分ける
「見えにくくて、近い場所」を点数だけで決めると、丸見えでも近さで勝ってしまうかもしれません。隠れるために欠かせない条件と、できれば満たしたい条件を分けましょう。

| 段階 | EQSの要素 | 今回すること |
|---|---|---|
| 候補を作る | Generator | 敵の周囲に格子状の点を作る |
| 条件で除外する | TestのFilter | 見える点、歩いて行けない点を外す |
| 残りを比べる | TestのScore | 敵に近い点ほど高く採点する |
| 一つ選ぶ | Run Mode | 最高点の候補を移動先にする |
Filterは、候補として残してよいかの判定。Scoreは、残った候補の優先順位です。一つのTestで両方を行うこともできますが、最初は役割を分けると設定を追いやすくなります。
クエリ(Query) は、この「どんな条件で探すか」をまとめたものです。実行するたびに、その時点の周囲を調べます。EQSが選んだだけでは敵は動かず、結果をMove Toへ渡して初めて移動します。
Behavior Treeが「何をするか」、AI Perceptionが「何を見聞きしたか」、EQSが「どの候補を選ぶか」を担当する、と考えるとつながります。
まずは候補点を表示する
実践の土台には、Behavior Tree記事の BP_Enemy、BP_EnemyAIController、BB_Enemy、BT_Enemy を使います。巡回と追跡が動き、NavMeshの上を敵が歩けるところまで用意してください。
テスト用のレベルには広い床と、敵の背丈より高いCubeの壁を2つほど置きます。壁の両端を回り込める幅を空け、壁の前後がNavMeshでつながるようにします。
1. Gridを作る
「Edit」→「Plugins」で Environment Query Editor を検索します。無効なら有効にして、再起動します。
コンテンツブラウザで右クリックし、「Artificial Intelligence」→「Environment Query」を作ります。名前は EQ_HideSpot です。開いたらRootから線を延ばし、Points: Grid を追加します。
| 項目 | 設定 |
|---|---|
| Generate Around | EnvQueryContext_Querier |
| Grid Half Size | 800 |
| Space Between | 150 |
| Projection DataのTrace Mode | Navigation |
Querier(クエリア) は、このクエリを実行している側です。敵が実行すれば敵、後で置くTesting Pawnが実行すればそのPawnが基準になります。
Grid Half Sizeは中心から端までの広さ、Space Betweenは点の間隔です。標準の単位はcmなので、ここでは周囲8mまで、約1.5mおきに探します。
投影(Projection) は、作った点をNavMesh上の位置へ合わせる処理です。ただし、別の足場にある点まで歩いて行けるとは限りません。道がつながるかは、後で別に調べます。

2. 近さだけで採点してみる
Gridを右クリックして「Add Test」→「Distance」を追加します。
| 項目 | 設定 |
|---|---|
| Test Purpose | Score Only |
| Distance To | EnvQueryContext_Querier |
| Test Mode | Distance 2D |
| Scoring Equation | Linear |
| Scoring Factor | -1.0 |
Distanceは、基準から候補までの距離を比べます。ここでは平面上の距離を使い、近い点を優先するためにScoring Factorを-1にします。
次にBlueprint Classを作り、「All Classes」で EQSTestingPawn を選び ます。名前は BP_EQSTester とし、レベルの床の上へ配置します。
そのPawnを選び、詳細で次のように設定してください。
| 項目 | 設定 |
|---|---|
| Query Template | EQ_HideSpot |
| Querying Mode | All Matching |
| Draw Labels | オン |
| Draw Failed Items | オン |
これは、実際の敵の代わりにクエリを試し、候補点を表示するPawnです。Nav Agentの体格も、実際の敵に合う値を使います。
保存してPawnを少し動かすと、その位置を基準に候補と点数が更新されます。この段階では、近くの点ほど好条件になっていれば成功です。+1へ変えると遠くが優先されるので、一度比べてから-1へ戻してみてください。

表示では、残った候補が赤から緑で採点され、緑ほど好条件です。青は、テストで除外された点です。青の濃淡を得点の高さとして読みません。色だけでなく、Draw Labelsの数値も確かめましょう。
誰から隠れるかを決める
近い場所が選べたので、次は「プレイヤーから見えない」という条件を加えます。そこで必要なのが Context(コンテキスト)=比較の基準となる相手や位置です。
今回は、候補の生成と距離の採点は「自分」、見え るかどうかは「プレイヤー」を基準にします。

プレイヤーを返すContextを作る
「Player」というContextが最初から用意されているわけではありません。自分のプレイヤーを渡すBlueprintを作ります。
ここでは、編集画面でも試せるよう、プレイヤー用の BP_ThirdPersonCharacterをレベルへ1体だけ配置します。すでに置いてある場合は、その1体を使ってください。その配置したActorの「Auto Possess Player」を Player 0 にし、Playしたらその体を操作できるようにします。Play中のアウトライナでも、このClassのプレイヤーが1体であることを確かめます。
BP_EnemyはプレイヤーBlueprintを複製して作った別Classなので、次の検索には含まれません。子Classとして作った敵を使っている場合は、プレイヤーだけを取得できるClassに置き換えてください。
- Blueprint Classの「All Classes」で EnvQueryContext_BlueprintBase を選ぶ。名前は EQC_PlayerContext。
- 「My Blueprint」の「Override」から Provide Actors Set を開く。
- 入口から Get All Actors Of Class、そこからReturn Nodeへ白い実行線をつなぐ。
- Actor Classを BP_ThirdPersonCharacter にする。
- 「Out Actors」をReturn Nodeの「Resulting Actors Set」へつなぎ、コンパイル・保存する。

Actors Setは、基準にするActorの一覧です。今回は1体だけを置くので、一人のプレイヤーが返ります。Play前にもそのActorが存在するため、Testing Pawnから同じContextを使えます。
Get Player Characterを返す方法もありますが、通常はPlay中に使うものです。編集画面でまだ生成されていないプレイヤーを探しても、基準を渡せません。今回は可視化を先に試すため、配置済みのActorを使っています。
見えない・歩ける・近いを組み合わせる
1. Traceで、壁の向こう側を残す
EQ_HideSpotのGridへ Trace テストを追加します。
| 項目 | 設定 |
|---|---|
| Test Purpose | Filter Only |
| Context | EQC_PlayerContext |
| Bool Match | オン |
| Trace Mode | Geometry by Channel |
| Trace Channel | Visibility |
| Trace Shape | Line |
| Trace from Context | オン |
| Item Height Offset | 90 |
| Context Height Offset | 0 |
Traceは、プレイヤーと候補点の間に線を引き、途中で何かに当たるかを調べます。今回のBool Match=オンは、「線が遮られる点を残す」という意味です。オフにすると、遮られずに見通せる点を残す設定になります。

線を床すれすれに引くと、段差まで隠れ場所だと判定することがあります。Item Height Offsetの90は、候補側の始点・終点を床から90cm上げる設定です。プレイヤー側はActorの位置をそのまま使います。今回のThird Person Characterでは、足元ではなく体の中心付近が基準です。
壁にはCollisionがあり、「Visibility」をBlockする必要があります。Traceの仕組みを確かめたい場合は、Line Traceの記事も参考になります。
これは、指定した高さの一本の線が遮られるかという簡単な隠れ先の判定です。頭や体全体が完全に隠れることまでは保証しません。最初は背の高い壁を使うと、結果を判断しやすくなります。
2. Pathfindingで、道がつながる点を残す
Gridへ Pathfinding テストを追加します。
| 項目 | 設定 |
|---|---|
| Test Purpose | Filter Only |
| Test Mode | Path Exist |
| Context | EnvQueryContext_Querier |
| Path from Context | オン |
| Bool Match | オン |
| Skip Unreachable | オン |
Path Existは、 そこまでの経路があるかの確認です。NavMesh上の別の島に候補があっても、渡る道がなければ外せます。

最初に作ったDistanceは、Distance ToがQuerier、Scoring Factorが-1のままです。「プレイヤーに近い場所」へ変えないようにしてください。
これでクエリは、次の役割分担になります。テストの実行順はEQSが調整するため、画面の並びをBlueprintの実行線のようには読みません。
| 要素 | 担当 |
|---|---|
| Grid | 自分の周囲に候補を作る |
| Trace:Filter Only | プレイヤーからの線が遮られる点を残す |
| Pathfinding:Filter Only | 自分から歩いて行ける点を残す |
| Distance:Score Only | 残った中で、自分に近い点を高くする |
3. 壁の配置を変えて確かめる
BP_EQSTesterを壁の手前、プレイヤーを反対側に置き、Query Templateを指定して結果を見ます。近い点でもプレイヤーから丸見えなら除外され、壁で遮られた到達可能な点が残れば、狙った条件になっています。

図の点数は、近い候補ほど高くなることを示す例です。実際の数値は候補の配置によって変わります。
次はプレイヤーを壁の横へ動かし、Testing Pawnも少し動かして再計算します。さっきまで隠れられた点が除外されるかを見てください。壁を動かした場合はNavMeshの更新も待ちます。
大きなクエリを編集中、表示が重いときはTesting PawnのQuery Templateをいったん外します。範囲を広げる前に、候補の間隔を粗くして試しましょう。
実践:被弾したら隠れ先へ動く
ここまでで「どこに隠れるか」ができました。次は「被弾したら探し、移動して3秒待つ」をBehavior Treeへ組み込みます。隠れ先がない場合や移動に失敗した場合も、その場で3秒待って通常の行動へ戻します。

1. Blackboardに2つのKeyを追加する
BB_Enemyを開き、次を追加します。
| Key | 型 | 用途 |
|---|---|---|
| NeedsCover | Bool | 隠れる処理を始める合図。初期値はfalse |
| HideLocation | Vector | EQSで選んだ移動先 |
Blackboardは、Behavior Treeが読む共有メモです。NeedsCoverは「隠れる必要があるか」、HideLocationは「どこへ隠れるか」と分けます。Vectorは位置を表すX・Y・Zの組です。
2. 被弾を合図へ変える
BP_Enemyで「Can Be Damaged」 がオンになっていることを確認し、Event AnyDamage を追加します。
- Get Controllerの出力を、Get Blackboard のTargetへつなぐ。
- Get BlackboardのReturn Valueを、Set Value as Bool のTargetへつなぐ。
- Set Value as BoolのKey Nameを
NeedsCover、Bool Valueをtrueにする。 - Event AnyDamageからSet Value as Boolへ、白い実行線をつなぐ。

Event AnyDamageは、Apply Damageなどでダメージを受け取ったときに呼ばれます。ここではHPを減らす処理と別に、隠れる合図を記録します。すでに同じイベントがある場合は、既存処理へこの更新を追加してください。
3. 最後に合図を戻すTaskを作る
BT_Enemyの「New Task」から、BTTask_BlueprintBase を親にした BTT_EndCover を作ります。
変数 FlagKey を Blackboard Key Selector 型で追加し、Instance Editableをオンにします。これは、Taskを置いた場所で「どのKeyを操作するか」を選ぶための変数です。
Receive Execute AI → Set Blackboard Value as Bool → Finish Execute と白い実行線をつなぎます。SetのKeyにFlagKey、Valueにfalseを指定し、Finish ExecuteのSuccessはオンです。

図のSet BB Boolは、Set Blackboard Value as Boolを短く表記したものです。これで、隠れる処理が終わったあと、NeedsCoverをfalseへ戻せます。Finish Executeは「このTaskは終わった」と木へ知らせるノードです。
4. 隠れる枝を一番左へ置く
BT_Enemyの最上位Selectorへ、新しい Sequence「隠れる」 を追加します。追跡や巡回より優先したいので、既存の枝より左に置きます。
「隠れる」にBlackboard Decoratorを追加します。
| 項目 | 設定 |
|---|---|
| Blackboard Key | NeedsCover |
| Key Query | Is Set |
| Notify Observer | On Result Change |
| Observer Aborts | Lower Priority |
BoolのIs Setは、ここではtrueかどうかの判定です。Lower Priorityにより、NeedsCoverがtrueになると、右側で実行していた追跡や巡回を中断して隠れる枝へ入ります。
「隠れる」の子には、左から次の3つを置きます。
- Sequence 「隠れ先へ移動」
- Wait:Wait Time=3.0、Random Deviation=0
- BTT_EndCover:FlagKey=NeedsCover
さらに「隠れ先へ移動」の子へ、Run EQS QueryとMove Toを兄弟として左から並べます。Run EQS Queryの下へMove Toをぶら下げる構造ではありません。

Run EQS Queryは次の設定です。
| 項目 | 設定 |
|---|---|
| Query Template | EQ_HideSpot |
| Run Mode | Single Best Item |
| Blackboard Key | HideLocation |
| Update BBOn Fail | 表示される場合はオン |
Single Best Itemで、残った候補の最高点を一つ選びます。Move ToのBlackboard Keyも HideLocation にし、Acceptable Radiusはまず30、Reach Test Includes Agent RadiusとGoal Radiusはオフ、Allow Partial Pathはオフにします。選んだ点の近くまで行くための設定です。
最後に、内側のSequence「隠れ先へ移動」へ Force Success Decoratorを付けます。
EQSで候補が見つからなければ、そのSequenceは失敗し、Move Toへ進みません。移動に失敗した場合も含め、Force Successで外側には成功として返し、次のWaitとBTT_EndCoverを実行させます。隠れることに成功した、という判定ではなく、後始末へ進めるための設定です。

これにより、候補がないときにクエリだけを間隔なく繰り返すことを避けられます。以前のHideLocationが残っていても、クエリに失敗した経路ではMove Toへ進みません。
5. Nキーでダメージを与えてみる
銃の代わりに、レベルの敵1体をNキーで攻撃して試します 。
- レベルでBP_Enemyを選び、Level Blueprintを開く。
- グラフを右クリックし、その配置した敵の参照を作る。
- NイベントのPressedを Apply Damage へつなぐ。
- Damaged Actorに敵の参照、Base Damageに
1を指定する。その他の入力は、この動作確認では既定のままでよい。

Blueprintをコンパイルして保存し、Behavior Treeも保存します。Playしてゲーム画面をクリックし、敵とプレイヤーが互いに見える位置でNを押してください。Nを別の操作で使っている場合は、この確認用イベントを空いているキーへ変えます。
| 試すこと | 見る結果 |
|---|---|
| Nでダメージを与える | NeedsCoverがtrueになり、隠れる枝へ入る |
| 隠れ先がある | HideLocationが更新され、敵がその位置へ移動する |
| 移動できた | 到着後3秒待ち、追跡や巡回へ戻る |
| 壁をなくして候補をなくす | Move Toへ進まず、その場で3秒待って戻る |
| 壁の位置を変えて再度試す | 次のクエリで隠れ先を選び直す |
この例では、隠れている途中の被弾で待ち時間を延長しません。NeedsCoverがすでにtrueの間は、その一回の処理を続けます。
実際の弾がApply Damageを呼ぶようになったら、Nキーの代わりに同じ被弾処理が使えます。
うまくいかないときの見方
まず、Testing Pawnで「候補が選べるか」、次にBehavior Treeで「その結果を使えているか」を分けて見ます。
| 症状 | 確認するところ |
|---|---|
| 候補点が出ない | Query Template、Gridの範囲、Navigationへの投影、NavMesh、Testing Pawnの位置 |
| Traceを足すと何も残らない | ContextがActorを返しているか、プレイヤーが1体配置されているか、壁がVisibilityをBlockするか |
| 丸見えの場所が残る | Bool Match、Traceの高さ、意図しない床や小物に線が当たっていないか |
| 遠い点が選ばれる | Distance ToがQuerierか、Scoring Factorが-1か |
| Previewでは成功、敵は移動できない | Testing Pawnと敵の体格、Path Exist、実行時のNavMesh、Move Toの結果 |
| ダメージを受けても枝が変わらない | Can Be Damaged、実行中のBlackboardのNeedsCover、枝の左右、Observer Aborts |
| 隠れたあと動かない | Waitの時間、BTT_EndCoverのFlagKey、Finish Executeの接続 |
実行中はAI DebuggingのEQS表示でも、候補の点数や選ばれた点を追えます。既定ではアポストロフィキーでAI Debuggingを開き、テンキー3でEQS表示を切り替えます。キーボード配列や設定でキーが異なる場合は、プロジェクトのGameplay Debugger設定を確認してください。