敵が部屋を歩き回り、プレイヤーを見つけると追ってくる。壁の裏に隠れると、追うのをやめてまた歩き出す。こうした敵を作るときは、移動そのものに加えて「いつ、どの行動へ切り替えるか」を考える必要があります。
Behavior Tree(ビヘイビアツリー) は、その行動の選び方を木の形で組む仕組みです。「相手が見えているなら追う。そうでなければ巡回する」という方針を、枝の条件と並び方で表せます。
他のエンジンでいうと: Unity にも Godot にも、これに当たる標準機能はありません(アセットや自作で補います)。 エンジンに最初から入っている ことが、UEでAIを組むときの利点になります。
この記事では、まず巡回だけを動かし、次にプレイヤーの発見と追跡を足します。最後は近づいたときに「攻撃」と表示させ、行動が切り替わったことまで確かめましょう。
この記事でわかること
- AI Controller・Behavior Tree・Blackboardの役割分担
- SelectorとSequenceで木を読み解く方法
- 巡回だけを先に動かし、あとから追跡の枝を足す手順
- Observer Abortsで、行動の途中でも切り替える仕組み
この記事で作るもの
- 周囲の行き先を選び、移動して2秒待つ敵
- プレイヤーを見つけると巡回を中断して追い、近づくと攻撃のログを出す
- 見失うと追跡を中断して巡回へ戻る
UE5のThird Personテンプレートを使う単独プレイの例です。攻撃アニメーションやダメージは、このAIの判断が動いてから追加できます。
AIを動かす3つの役割
Behavior Treeだけでは、画面の敵は動きません。操作する役、行動を選ぶ役、判断材料を覚える場所を組み合わせます。

| 部品 | 今回の役割 |
|---|---|
| AI Controller | 敵の体を操作し、Behavior Treeを起動する |
| Behavior Tree | 条件を見て、巡回や追跡などの行動を選ぶ |
| Blackboard | 「誰を追うか」「どこへ歩くか」を保存する |
操作される体は Pawn と呼びます。今回使う Character は、歩行などの移動機能を持つPawnの一種です。「AI Controllerが中の人、Characterが体」と考えると、役割を分けやすくなります。
Blackboardは、AIが読む共有メモのようなものです。たとえば、目で相手を見つける処理が「追う相手」を書き込み、追跡の処理がそこを読んで移動します。相手を見つけた処理から、巡回・追跡のすべてへ個別に指示を送る必要がなくなります。
SelectorとSequence:木の読み方
木は上の Root(入口) からたどります。同じ親につながる子は、左から順に試します。まず覚えたいのが、子の結果をどう扱うかを決める2種類です。

| 種類 | 子が成功したら | 子が失敗したら |
|---|---|---|
| Selector | そこで成功して終わる | 右の子を試す。全部失敗なら失敗 |
| Sequence | 右の子へ進む。全部成功なら成功 | そこで失敗して終わる |
Selectorは「どの行動を選ぶか」 を表します。左に「プレイヤーを追う」、右に「巡回する」を置けば、追う条件が成立する限り、そちらが先に選ばれます。
Sequenceは「選んだ行動を、どんな手順で進めるか」 を表します。巡回なら「行き先を決める→そこまで移動する→少し待つ」です。移動が失敗した場合は、その後の待機へ進まず、この手順を失敗として終えます。
「実行中」は、成功でも失敗でもない
移動など、ひとつの行動を担当する部品を Task と呼びます。敵が目的地へ歩いている間、移動のTaskは 実行中(Running / In Progress) です。移動指示を出しただけでは成功にならず、到着するまで次のTaskへ進みません。

これはSelectorでも同じです。左の追跡が実行中だからといって、右の巡回も始めるわけではありません。ひとつのTaskの結果を待ち、成功か失敗が決まってから先へ進むのが、今回の木の基本です。途中で行動を変える方法は、後半で足します。
Task・Decorator・Serviceの違い
木の部品には、行動するものだけでなく、条件を確認するものもあります。

| 部品 | 役割 | 例 |
|---|---|---|
| Task | 行動を実行する | Move Toで移動、Waitで待機 |
| Decorator | 枝やTaskを実行してよいか確かめる | 「追う相手がいる」なら通す |
| Service | 取り付けた枝が動いている間、繰り返し確認する | 一定間隔で相手との距離を測る |
Decoratorは条件、Taskは行動と分けると、木を読んだときに判断の流れが見えます。ただし、Taskが実行直前に対象を確かめ、無効なら失敗を返すこともあります。
Serviceは、その枝を離れると止まります。どの行動中でも必要な確認なら、両方の枝をまとめる親へ付けます。今回の発見と見失いは、後ほど説明するAI Perceptionの通知を使うため、Serviceを自作する必要はありません。
敵と、歩ける場所を用意する
敵の見た目と操作役を作る
Third Personテンプレートを開き、作業用に「AI」フォルダを作ります。
- 「BP_ThirdPersonCharacter」を複製し、「BP_Enemy」と名付けます。敵の見た目や歩行アニメーションを流用できます。
- BP_Enemyを開き、EventGraphの入力処理などのノードを削除します。複製元のプレイヤーはそのまま使います。
- 新しい「Blueprint Class」を作り、「All Classes」で「AIController」を検索して選びます。名前は「BP_EnemyAIController」です。
- BP_Enemyの「Class Defaults」で、下表の操作設定を指定します。Character Movementの2項目は、「Components」でそのコンポーネントを選んで設定します。
| 設定 | 値 |
|---|---|
| AI Controller Class | BP_EnemyAIController |
| Auto Possess AI | Placed in World or Spawned |
| Auto Possess Player | Disabled |
| Use Controller Rotation Yaw | オフ |
| Character Movement → Orient Rotation to Movement | オン |
| Character Movement → Max Walk Speed | 300 |
Possessは、ControllerがPawnの操作を受け持つことです。Auto Possess AIの設定により、レベルへ置いた敵にも、あとから生成した敵にもAI Controllerが付きます。向きの設定は、歩く方向へ敵を向かせるためのものです。UEの距離は標準ではcmなので、Max Walk Speedの300は秒速3mにあたります。
AIが歩ける地図を作る
レベルの平らな場所へ敵を1体置き、「Nav Mesh Bounds Volume」を追加して、敵とプレイヤーが歩く床全体を覆います。P を押し、床に緑の範囲が表示されるか確認してください。
この NavMesh は、AI用の「歩ける場所の地図」です。見た目が床でも、ここに地図がなければMove Toで歩けません。緑にならない場合は NavMeshの記事 を先に確認しましょう。

BlackboardとBehavior Treeを作る
コンテンツブラウザを右クリックし、「Artificial Intelligence」から「Blackboard」と「Behavior Tree」を作ります。名前は「BB_Enemy」「BT_Enemy」です。
BB_Enemyを開き、「New Key」で次の2項目を作ります。Keyは、保存する項目の名前です。
| Key名 | 型と設定 | 保存するもの |
|---|---|---|
| TargetActor | Object、Base ClassをActor | 追いかける相手 |
| PatrolLocation | Vector | 次に歩く座標 |
Object には、「レベルにいる、あの相手」を指し示す参照を入れます。名前の文字列を入れる欄ではありません。Vector はX・Y・Zの3つの値で、ここでは場所を表します。
BT_Enemyを開き、「Blackboard Asset」にBB_Enemyを指定して保存します。これで、木から2つのKeyを選べるようになります。
まず巡回だけを動かす
行き先を選ぶTaskを作る
BT_Enemyの「New Task」から「BTTask_BlueprintBase」を選び、「BTT_FindPatrolLocation」と名付けます。Task側で変数「PatrolKey」を作り、型を Blackboard Key Selector、「Instance Editable」をオンにします。
Blackboard Key Selectorは「どのKeyへ書くか」 を選ぶための型です。Instance Editableを有効にすると、木に置いたTaskの詳細から「PatrolLocation」を指定できます。
TaskのEventGraphに「Event Receive Execute AI」を追加します。このイベントが「このTaskを始めて」という合図です。「Controlled Pawn」には、操作している敵の体が渡ってきます。
まずは次の接続で、書き込む座標を選びます。
- Controlled Pawn → Get Actor Locationの「Target」。敵の現在位置を取得します。
- 現在位置 → Get Random Reachable Point in Radiusの「Origin」。「Radius」は1000(半径10m)にします。
- 「Select Vector」を追加し、「A」へRandom Location、「B」へ敵の現在位置、「Pick A」へ探索のReturn Valueをつなぎます。

Get Random Reachable Point in Radiusは、周囲から歩いて行ける地点を探します。Return Valueがtrueなら見つかった、falseなら見つからなかったという意味です。Select VectorはtrueならA、falseならBを選ぶので、失敗時には現在位置を行き先にできます。
次に、選んだ座標を書いてTaskを終えます。
- Receive Execute AIの白い実行線 → Set Blackboard Value as Vector → Finish Execute。
- Set Blackboard Value as Vectorの「Key」へPatrolKeyのGet、「Value」へ先ほどのSelect VectorのReturn Valueをつなぎます。
- Finish Executeの「Success」をオンにします。

Finish Executeは、Taskが終わったことを木へ返すノードです。ここでの成功は「行き先を書けた」という意味で、敵が到着したという意味ではありません。移動は次のMove Toに任せます。
探索に失敗した場合も、現在位置へ移動したあとで待ち時間に入れます。すぐに探索をやり直し続けるのを避けられます。Compileして保存してください。
巡回の木を組み、起動する
BT_EnemyでRootから線を引き、Selectorを追加します。その子にSequenceを置き、「Node Name」を「巡回する」にします。Sequenceの子を、左から次の順で並べます。
BTT_FindPatrolLocation → Move To → Wait
| ノード | 設定 |
|---|---|
| BTT_FindPatrolLocation | Patrol KeyをPatrolLocation |
| Move To | Blackboard KeyをPatrolLocation、Acceptable Radiusを50 |
| Wait | Wait Timeを2.0、Random Deviationを0 |

Move Toの Acceptable Radius は、「どれだけ近づけば到着とみなすか」です。位置を完全に一致させようとせず、ある程度近づいたところで止めます。
最後にBP_EnemyAIControllerのEventGraphへ「Event On Possess」を追加し、「Run Behavior Tree」へつなぎます。「BTAsset」はBT_Enemyです。

On Possessは、操作するPawnを受け持ったときに呼ばれます。この時点で木を起動すれば、どの敵を操作するかが決まった状態で巡回を始められます。
Compile・保存してPlayしましょう。敵が歩き、到着すると2秒待ち、また次の場所へ歩けば成功です。 まだプレイヤーを見つける処理はないので、近づいても巡回を続けます。
プレイヤーを見つけたら、Blackboardへ覚える
巡回が動いたら、敵に視覚を足します。AI Perception は、見えた相手や聞こえた音などの情報を受け取る仕組みです。今回は視覚だけを使います。

見える距離と角度を設定する
BP_EnemyAIControllerに「AI Perception」コンポーネントを追加します。「Senses Config」に「AI Sight config」を1つ追加し、次の値を設定します。
| 項目 | 値と意味 |
|---|---|
| Sight Radius | 1500:新しく相手を見つける距離の上限 |
| Lose Sight Radius | 1800:見つけた相手を、距離で見失う上限 |
| Peripheral Vision Half Angle | 60:正面から左右に各60度、合計約120度 |
| Detection by Affiliation → Detect Neutrals | オン:チーム設定をしていない相手も対象にする |
| Starts Enabled | オン |
Sight Radiusの1500は15mです。Lose Sight Radiusは、壁の裏でも見える設定ではありません。 視線を遮る壁で隠れれば、距離内でも見失います。検証用の壁は「Visibility」チャンネルをBlockするものにします。
プレイヤー側のBP_ThirdPersonCharacterでは、次を設定します。
- 「Class Defaults」のActorの「Tags」へ「Player」を追加する。Component Tagsとは別です。
- 「AI Perception Stimuli Source」を追加し、「Auto Register as Source」をオン、「Register as Source for Senses」へ「AISense_Sight」を追加する。
Stimuli Sourceは、感知される側として登録するための部品です。ここでPlayerタグも付けておくと、感知したActorがプレイヤーかどうかを判別できます。BP_EnemyにはPlayerタグを付けません。
通知から、相手と視覚の結果を受け取る
BP_EnemyAIControllerに関数「ApplySightUpdate」を作ります。入力は「SeenActor」(Actor Object Reference)と「bSensed」(Boolean)の2つです。Booleanはtrue/falseの値で、ここでは見えているかどうかを表します。
AI Perceptionコンポーネントの詳細にある「On Target Perception Updated」の「+」からイベントを追加し、ApplySightUpdateを呼びます。
- イベントのActor → 関数のSeenActor。
- Stimulus → Break AIStimulus → Successfully Sensed → 関数のbSensed。

Stimulusは、今回の感知結果をまとめたデータです。「Successfully Sensed」がtrueなら見つけた、falseなら見失った通知として扱います。ここでは視覚のみを設定しているため、音の通知が混ざることはありません。
相手を記録する・消す
ApplySightUpdateの中では、まずSeenActorを「Actor Has Tag」のTargetへ渡し、「Tag」をPlayerにします。結果をBranchのConditionへつなぎます。関数の入口から、このBranchへ白い実行線をつなぎ、Trueの場合だけ2つ目のBranchへ進めてください。2つ目のConditionにはbSensedを渡します。
SeenActorとbSensedは、関数の入口にある出力ピンから取り出せます。Actor Has Tagは値を調べるノードなので、白い実行線は通しません。 白い線は処理の順番、色付きの線は相手や判定結果などのデータ、と分けて読むと接続を追いやすくなります。

2つ目のBranchのtrue側では、「Get Blackboard」(TargetはSelf)の戻り値から「Set Value as Object」を作ります。「Key Name」はTargetActor、「Object Value」はSeenActorです。

false側では、「見失った相手が、いま追っている相手か」を確かめてから消します。
- Get Blackboardの戻り値から「Get Value as Object」を作り、「Key Name」をTargetActorにします。
- そのReturn ValueとSeenActorを「Equal (Object)」の2つの入力へつなぎます。このノードは、同じ実体を指していればtrueを返します。
- 3つ目のBranchを置き、Equal (Object)の結果をConditionへつなぎます。bSensedで分けたBranchのFalseから、白い実行線をつなぎます。
- 3つ目のBranchのTrueから「Clear Value」へ進め、「Target」にGet Blackboardの戻り値、「Key Name」にTargetActorを指定します。False側はつなぎません。

Get Value as Object、Set Value as Object、Clear Valueの Targetは、Get Blackboardで得たBlackboard Component です。これは、このAIのメモを読み書きするための部品です。敵のCharacterをつなぐ場所ではありません。
「追っている相手を見失ったときだけ消す」としておけば、別のActorの通知で追跡対象が消えるのを防げます。保存と消去の処理は、同じApplySightUpdate内で、bSensedのBranchのTrue側・False側へそれぞれつなぎます。
ここまでをCompile・保存します。Play中にBT_Enemyを開き、Blackboardの TargetActorが、発見時にプレイヤーになり、見失うとNoneへ戻れば成功です。Noneは「参照する相手が入っていない」状態です。敵の行動はまだ巡回のままでも構いません。
追跡の枝を足し、途中で切り替える
攻撃の代わりに、ログを出すTaskを作る
新しいTaskを作り、「BTT_Attack」と名付けます。「Event Receive Execute AI」から「Print String」へつなぎ、「In String」を「攻撃」にします。続いて「Finish Execute」へつなぎ、「Success」をオンにします。Compileして保存してください。

これで、木が攻撃のTaskを選んだタイミングを画面やOutput Logで確認できます。今回はダメージを与えず、判断が正しく動くかを見ます。
巡回より左に、追跡の手順を置く
BT_EnemyのSelectorに、もうひとつSequenceをつなぎます。「Node Name」を「追って攻撃する」にし、巡回より左へ置いてください。子は「Move To」「BTT_Attack」「Wait」の順です。

追跡側のMove Toは、次の設定にします。
| 設定 | 値 |
|---|---|
| Blackboard Key | TargetActor |
| Acceptable Radius | 200 |
| Reach Test Includes Agent Radius | オフ |
| Reach Test Includes Goal Radius | オフ |
| Track Moving Goal | オン |
| Allow Partial Path | オフ |
到着判定へ双方のカプセル半径を加えない設定にし、約2mまで近づいたら次のTaskへ進ませます。Track Moving Goalは、移動中の相手に合わせて経路を更新する設定です。Allow Partial Pathは「目標の途中までしか行けない経路」を許可する項目で、今回はオフにします。
攻撃後のWaitは「Wait Time」を1.0、「Random Deviation」を0にします。相手が近くにいる間は、次の攻撃まで約1秒待つようになります。実際のダメージ処理を足す場合は、攻撃時にも相手・距離・向きを確かめる処理が必要です。
条件が変わったら、今の行動を中断する
「追って攻撃する」のSequenceを右クリックし、「Add Decorator」→「Blackboard」を追加します。以下を指定します。
| 設定 | 値 |
|---|---|
| Blackboard Key | TargetActor |
| Key Query | Is Set |
| Notify Observer | On Result Change |
| Observer Aborts | Both |
Is Setは、追う相手が入っているかを確かめる条件です。しか し、条件を付けるだけでは、実行中の巡回を途中で止めることはできません。
そこで使うのが Observer Aborts(条件を監視して中断する設定) です。Bothには、今回ほしい2方向の中断が含まれます。
- 相手が入ったら、右の巡回を中断し、左の追跡へ切り替える。
- 相手が消えたら、この追跡の枝を中断し、巡回へ戻る。

「見つける処理がBlackboardへ書く→条件が変わる→実行する枝が切り替わる」という流れになりました。視覚の処理は相手の情報を更新し、行動の切り替えは木に任せられます。
動かして、木と値を見比べる
BT_Enemyを開いたままPlayし、エディタ上部のデバッグ対象から、置いた敵の実体を選びます。実行中のノードの強調表示と、Blackboardの現在値を見ながら、次の順で試してください。
| 操作 | 期待する結果 |
|---|---|
| 敵の視界へ入らずに待つ | 巡回のMove ToとWaitを繰り返す |
| 敵の正面へ入る | TargetActorにプレイヤーが入り、追跡へ切り替わる |
| 近くで止まる | 「攻撃」が約1秒間隔で表示される |
| 壁で敵の視線を遮る | TargetActorがNoneになり、巡回へ戻る |
一度Playを止め、Observer Abortsを「None」に変えて試すのも理解の助けになります。 敵が巡回のWaitをしているときに正面へ出ると、相手の情報は更新されても、実行中の待機は中断されない様子を確認できます。見終えたらBothへ戻しましょう。

| つまずき | 見るところ |
|---|---|
| 敵が1歩も動かない | 緑のNavMesh、AI Controller Class、Auto Possess AI、On Possessの起動処理 |
| 巡回地点を選んだあと止まる | TaskのPatrol KeyがPatrolLocationか、Finish Executeを呼んだか |
| TargetActorがずっとNone | PlayerのActor Tag、Sightの距離と角度、Detect Neutrals、Stimuli Source |
| 相手は入るが巡回を中断しない | 左右の枝の順、DecoratorのObserver Aborts |
| 見失っても追い続ける | Clear Valueへ進んでいるか、そのTargetがBlackboardか |
| 壁越しに見つかる | 壁のVisibilityがBlockか |
| 攻撃が続けて表示される | 攻撃後のWait Timeが1.0か |
敵を増やす場合、TargetActorは敵ごとに別のBlackboardへ入ります。確認するときは、デバッグ対象が意図した敵かも見てください。
見失ったあとに最後の場所を調べさせたい、音にも反応させたい場合は AI Perceptionの記事 へ進めます。隠れる場所を選ばせるなら EQS、状態と遷移を中心に組む方式も 見たいなら StateTree が次の候補です。
おまけ:先に知っておくと良いこと
- 並び順がそのまま優先順位: Behavior Treeは左から順に試します。「見つけたら追跡」は「巡回」より 左 に置きます。条件が合っていても、並びが逆だと期待どおりに動きません
- 参照は最初にそろえる: AI Controller・Behavior Tree・Blackboardのつながりが1つでも抜けていると、見た目は組めていても動きません。Blackboard Keyの 名前と型 も、TaskやDecoratorと一致させます
- 切り替わらないときはObserver Aborts: 追跡を即中断したい、見つけた瞬間に切り替えたい。こうした「途中で割り込む」動きは、
Self/Lower Priority/Bothの違いで決まります - 1つずつ足す: 巡回だけを先に完成させ、動くのを見てから追跡の枝を足します。まとめて組むと、どこが原因か分からなくなります
まとめ
Behavior Treeでは、Selectorで行動を選び、Sequenceでその手順を進めます。Blackboardに置いた判断材料をDecoratorが読み、Taskが移動や待機を担当します。
今回の敵で特に確かめたいのは、「プレイヤーを見つけた」という情報と、「巡回をやめて追う」という行動が分かれていることです。まず値が変わったか、次に枝が切り替わったか。この順で見れば、行動が増えても原因を追いやすくなります。