「巡回中にプレイヤーを見つけたら追跡する。見失ったら少し待ち、また見つければ追跡へ戻る」。文章では短く書けるのに、Blueprintへ条件を足していくと、今どの処理をしているのか分かりにくくなることがあります。
StateTreeは、こうした「今の状態」と「次へ移るきっかけ」を整理する仕組みです。敵AIのほか、扉やクエストの進行にも使えます。

この記事でわかること
- StateTreeとBehavior Treeで「読む場所」が違うこと
- 状態の中身(Task)と、切り替わるきっかけ(Transition)の分け方
- 巡回・追跡・警戒待機の3状態をキー操作で試す手順
- データをどこに置き、条件へどう渡すか
今回は、キー操作で「見つけた」「見失った」を切り替える見張りモデルを作ります。まず画面の文字で状態の変化を確かめ、最後に感知や移動へつなぐ考え方を整理します。
- State、Task、Transitionが何を担当するか
- Actorの値を、状態が切り替わる条件へ渡す方法
- 警戒中に再発見したら、5秒の待機を中断する作り方
- Behavior Treeと、どちらで仕様を表すと読みやすいか
Behavior Treeと、読む場所が違う
Behavior Treeでは、「追う相手がいれば追跡を優先し、それ以外は巡回する」といった優先順位を、枝の並びで表します。
StateTreeでは、「巡回中に発見したら追跡へ」「追跡中に見失ったら警戒待機へ」という状態の切り替わりを中心に読みます。

| 見たいこと | Behavior Tree | StateTree |
|---|---|---|
| どの行動を優先するか | Selectorの枝と条件を読む | 状態の選択条件や遷移を読む |
| 今の処理が終わったらどうするか | Sequenceの次のTaskなどを読む | 完了時のTransitionを読む |
| 途中で別の行動へ移れるか | DecoratorのObserver Abortsなどで設定 | 状態から出るTransitionで設定 |
UEのBehavior Treeは、Blackboardの変化などをきっかけに処理を切り替えるイベント駆動です。「毎フレーム木全体を調べるBT」と「必要なときだけ動くStateTree」という分け方ではありません。ここでは、同じ仕様をどちらの構造で読みたいかに注目しましょう。
EnumとSwitchのステートマシンで状態を分けたことがあれば、StateTreeは、その状態ごとの処理と切り替えを専用エディタで組むもの、と考えると入りやすくなります。
状態の中身と、切り替わるきっかけ
まずは、次の4つを押さえます。

| 要素 | 意味 | 今回の例 |
|---|---|---|
| State(状態) | 今、何をしているか | Patrol、Chase、Search |
| Task | その状態で行う処理 | 文字を表示する、5秒待つ |
| Condition(条件) | 値を調べ、成立するか決める | PlayerVisibleがtrueか |
| Transition(遷移) | いつ、どの状態へ移るか | 見失ったらChaseからSearchへ |
遷移(せんい) は、状 態が切り替わることです。Transitionでは、調べるきっかけの「Trigger」、行き先の「Transition To」、必要なら「Conditions」を設定します。
条件にも置き場所があります。Enter Conditionsは、その状態へ入れるかを調べます。TransitionのConditionsは、別の状態へ移るかを調べます。入場条件を書いただけで、実行中の状態から自動的に出ていくわけではありません。
もう一つ、Taskの並べ方にも違いがあります。同じStateに置いたTaskは、順番待ちの一覧ではありません。 文字を出しながら時間を数える、というように並行して動きます。今回のSearchでは、この性質を使って「警戒中の表示」と「5秒の待機」を組み合わせます。
最初は「巡回中」と表示してみる
実験には、Third Personなど、Playしてレベル内を見られるプロジェクトを使います。Blueprintで変数とキー入力を追加できれば進められます。
1. プラグインと、実行するActorを用意する
「Edit」→「Plugins」でStateTreeを検索し、StateTreeとGameplayStateTreeを有効にして、再起動します。
次にActorを親とするBlueprint BP_GuardStateDemo を作ります。位置が分かるようにStatic MeshのCubeを1つ追加し、プレイヤーの近くへ配置してください。これは状態を観察するための見張り役です。
Boolean変数 PlayerVisible を追加し、初期値をfalse、Instance Editableをオンにします。Booleanはtrue/falseの2値で、今回は「プレイヤーが見えるか」を表します。コンパイル・保存しておきます。
2. SchemaとComponentをそろえる
コンテンツブラウザで右クリックし、「Artificial Intelligence」→「StateTree」から ST_GuardStateDemo を作ります。Schemaには StateTreeComponentSchema を選びます。
Schema(スキーマ) は、そのStateTreeをどこで使い、どんなデータを受け取れるかを決める設定です。今回は普通のActorで実行する組み合わせを使います。

- ST_GuardStateDemoを開き、Schemaの「Context Actor Class」を BP_GuardStateDemo にする。
- BP_GuardStateDemoへ StateTree Component を追加する。
- コンポーネントの「State Tree」に ST_GuardStateDemo を指定する。
- 「Start Logic Automatically」が表示される版ではオンにする。
Contextは、StateTreeが実行相手として受け取るデータです。今回のActorは、レベルへ配置したBP_GuardStateDemoです。Classを指定しておくと、後でそのActorのPlayerVisibleを条件から選べます。
StateTree AI ComponentはAI Controller用です。今回使う通常のStateTree Componentとは、Schemaも組み合わせも異なります。
3. Patrolを1つ作る
ST_GuardStateDemoのRootを右クリックし、「Add Child State」から Patrol を追加します。Rootは木の入口、Patrolはその子です。
Patrolを選び、詳細の「Tasks」に Debug Text Task を追加します。「Text」を Patrol にし、Text Colorは見やすい色、Font Scaleは読みやすい大きさにします。このTaskは、実行中の状態を文字で確認するために使います。
Patrolの「Transitions」に最初から項目があれば、この段階では削除します。Enter Conditionsも空にします。状態を1つだけ使い、表示を続ける形です。

StateTreeとBlueprintをコンパイル・保存し、Playします。Cubeの近くで Patrol の文字が出れば、アセットを作っただけでなく、配置したActorで実行できたことを確かめられます。
表示されないときは、アセットの割り当て、Schema、Context Actor Class、コンパイル結果を確認してください。Debug Text TaskにActorの指定欄がある場合は、ContextのActorを選びます。これは開発中の確認表示で、完成したゲームのUIは別に用意します。
実践:発見・見失い・再発見を試す
ここから状態を3つに増やします。Nキーを押すたびに、PlayerVisibleをtrue/falseへ切り替えます。
| 状態 | 今回表示するもの | 終わるきっかけ |
|---|---|---|
| Patrol | Patrol(巡回) | 見つけたらChaseへ |
| Chase | Chase (追跡) | 見失ったらSearchへ |
| Search | Search(警戒待機) | 5秒たったらPatrolへ。再発見ならChaseへ |
今回は「巡回」「追跡」という判断の変化を文字で確かめます。Cubeを歩かせる処理は、後半の「移動する敵へ広げるとき」で考えます。
1. Nキーで「見える」を切り替える
BP_GuardStateDemoの「Class Defaults」で「Auto Receive Input」を Player 0 にします。これで、Play中にこのActorがキー入力を受け取れます。実験用のActorはレベルへ1体だけ配置してください。
Event Graphで次の接続を作ります。
- Nイベントを置き、「Pressed」から Set PlayerVisible へ白い実行線をつなぐ。
- PlayerVisibleのGetから NOT Boolean を作る。
- NOTの出力をSet PlayerVisibleの値の入力へつなぐ。
NOTはtrueとfalseを反転する処理です。最初がfalseなので、1回押すとtrue、もう1回押すとfalseになります。

ここで変更するのは「見えるか」という事実だけです。NキーからPatrolやChaseを直接指定せず、次に作るStateTreeの条件に判断させます。
2. ChaseとSearchを追加する
Rootの下に Chase、Search を追加します。Patrol、Chase、Searchの3つを、同じ深さの子として上から並べてください。Patrolの中へChaseを入れないようにします。
| State | Tasks |
|---|---|
| Patrol | Debug Text Task:Text=Patrol |
| Chase | Debug Text Task:Text=Chase |
| Search | Debug Text Task:Text=Search、Delay Task:Duration=5.0 |
SearchのDelay Taskは、5秒たつと完了するTaskです。時間にばらつきを付ける項目があれば0、無期限に待つ項目があればオフにします。3つのStateのEnter Conditionsは、今回すべて空です。
次の図では親子関係を見やすく横に広げています。エディタの一覧では、Rootの下にPatrol、Chase、Searchが同じ深さで並べば大丈夫です。

3. PlayerVisibleを、遷移の条件へ結ぶ
Patrolの「Transitions」に項目を1つ追加し、次のように設定します。
| 項目 | 値 |
|---|---|
| Trigger | On Tick |
| Transition To | Chase |
| Conditions | Bool Compare |
| Bool CompareのLeft | Bind → Actor → PlayerVisible |
| Bool CompareのRight | true(オン) |
| Invert | オフ |
Binding(バインディング)は、値の渡し元を指定することです。「Leftには、このActorのPlayerVisibleを読んで入れてね」と結びます。左にtrueを手入力すると、プレイヤーの入力と関係なく、常にtrueとの比較になるので注意してください。

On Tickは、StateTreeの更新時に条件を確かめるきっかけです。今回はtrueになったら、PatrolからChaseへ移ります。更新されるたびに無条件でChaseへ入り直す、という意味ではありません。
同じ方法で、残りの遷移も作ります。比較に使うLeftは、すべてActorのPlayerVisibleです。
| 遷移元 | Trigger | 条件 | 行き先 |
|---|---|---|---|
| Patrol | On Tick | PlayerVisible=true | Chase |
| Chase | On Tick | PlayerVisible=false | Search |
| Search | On Tick | PlayerVisible=true | Chase |
| Search | On State Completed | なし | Patrol |
falseと比べる行はRightをオフにします。すべてInvertはオフです。最初からあったRootへ戻る遷移などは削除し、表の4本だけにそろえてください。
Searchの2本は、再発見してChaseへ移る遷移を上、完了してPatrolへ戻る遷移を下に並べます。Priorityを指定できる場合は再発見をHigh、完了をNormalにし、同時に成立した場合は再発見を優先させます。

4. 5秒待つ途中で、もう一度見つける
コンパイル・保存してPlayし、ゲーム画面をクリックしてからNキーを押します。
| 操作 | 表示の変化 |
|---|---|
| Play直後 | Patrol |
| Nを1回押す | Chase |
| もう1回押す | Search |
| そのまま5秒待つ | Patrol |
| Search中にNを押す | 5秒を待ち切らずChase |
| 再びNを押して見失う | Searchへ入り、新たに5秒待つ |
ここではSearchへ入った時点から5秒を数えます。再発見して別のStateへ移れば、SearchのTaskもその状態を抜けます。次にSearchへ入ったときは、Delay Taskが改めて待ち始めます。

同じStateに表示とDelayを入れたのは、「Searchと表示しながら5秒待つ」ためです。「文字表示が終わった後でDelayが始まる」という直列処理ではありません。
うまく切り替わらないとき
| 症状 | 確認するところ |
|---|---|
| Nを押しても変化しない | ゲーム画面に入力が届いているか、Auto Receive Input、Pressedの白い線 |
| Play直後からChaseになる | PlayerVisibleの初期値、Bool CompareのLeftが固定値になっていないか |
| Actorの変数をBindで選べない | Context Actor Class、変数の公開設定、BlueprintとStateTreeの再 コンパイル |
| Chaseから戻れない | Chase→Searchがfalseとの比較になっているか |
| Searchへ入るとすぐ戻る | DelayのDuration、不要な完了Taskや遷移を足していないか |
| Searchが終わらない | Delayの無期限設定、On State Completedの行き先 |
| 想定外の状態になる | 3つが同じ深さか、初期選択でPatrolが先に並んでいるか |
StateTreeのDebuggerでは、実行中のインスタンスを選び、どのStateが有効になったか、どの遷移を通ったかを確認できます。まずは文字表示で一巡を確かめてから、変化の履歴を追うと読みやすくなります。
データはどこに置き、どう渡すか
今回のPlayerVisibleは、StateTreeの中ではなくBP_GuardStateDemoの変数です。Nキーがその値を変え、StateTreeがContextのActorを通して読みます。
Parametersもありますが、Blackboardと同じ箱だと考えるより、値の役割で分けると理解しやすくなります。
| データ | 使い方の例 |
|---|---|
| Parameters | この見張りに渡す待ち時間や速度などの設定 |
| Context | 実行相手のActorやAI Controller、その相手が持つ値 |
| TaskやEvaluatorの出力 | 処理で選んだ目的地、集めた周囲の情報など |
Evaluatorは、条件やTaskが使う情報を用意するためのものです。今回はActorに必要な値があるため、自作Evaluatorを足さず、直接Bindしました。
図のParametersは、設定を外から渡す場合の例です。今回は待ち時間をDelay Taskへ直接入力しているので、Parametersの追加は不要です。

別々のStateへ同名の変数を作っても、自動で同じメモにはなりません。「値を変える場所はどこか」「条件が読む渡し元はどこか」を決めてから結びます。
移動する敵へ広げるとき
状態の変化が分かったら、発見の入力をAI Perceptionへ、状態中の処理を移動やアニメーションへ広げられます。
AI Controllerで動かす場合は、StateTree AI ComponentとStateTreeAIComponentSchemaを組み合わせます。今回のActor用アセットを、そのまま別Schemaのコンポーネントへ割り当てる手順ではありません。AI用のContextに合わせ、感知結果や移動相手のBindingも設定します。
| 今回の模型 | 動く見張りへ広げるとき |
|---|---|
| NキーでPlayerVisibleを反転 | 視覚の発見・見失いで更新する |
| 状態名のDebug Text | 移動、待機、アニメーションなどのTaskを用意する |
| Searchで5秒待つ | 調査先へ移動した後で、5秒待つ状態を作る |
| CubeのActor | AI Controllerが操作するCharacterを用意する |
移動に はNavMeshとAI Controllerの設定も必要です。Behavior Tree版から試す場合は、Run Behavior TreeとStateTreeの両方から同じ敵へ移動指示を出さないよう、実験用のControllerを分けます。
「移動してから待つ」は、状態を分ける
1つのStateにMove ToとDelayを並べると、移動と待機を同時に始める構成になります。目的地まで歩く間に5秒が終わっては、到着後の調査になりません。
そこで、親State「調査」の下に「移動」「待機」を置きます。移動の成功で待機へ移り、待機の完了で巡回へ戻る形です。移動失敗の行き先も決めます。

StateTreeには、子が有効な間、その親も有効になる階層があります。「調査中なら、移動中でも待機中でも再発見で追跡へ戻る」という共通の遷移は、親へまとめる設計ができます。
これはAI Perception記事の「調査先へ着いてから5秒待つ」動きへ広げるための構成例です。今回動かした模型の5秒は、見失ってSearchに入った時点から数えるものです。
どちらを選ぶか
StateTreeかBehavior Treeかを選ぶときは、作りたい敵の仕様を短く書いてみましょう。
| 仕様の見方 | 試しやすい選択 |
|---|---|
| 「待機中に発見したら追跡」「詠唱完了で攻撃」のように、状態の切り替わりを追いた い | StateTree |
| 「回復を最優先、次に退避、その次に攻撃」のように、優先順位を枝で読みたい | Behavior Tree |
| すでに動いているAIや、参考にするプロジェクトがある | その構成を土台にする |
| 状態が少なく、短いBlueprintで十分読める | EnumとSwitchのまま始める |
どちらでも複雑な行動は表せます。まずは小さな題材を作り、条件を一つ足したときに、変更する場所が分かるかで判断すると、自分のゲームに合う形を選びやすくなります。

おまけ:先に知っておくと良いこと
- 遷移条件の置き場所に気をつける: StateTreeは「いまどの状態か」が読みやすい代わりに、Transitionを置く場所を間違えると戻れない状態になります。共通の中断条件は親側へ置く と管理しやすくなります
- データはバインディングで渡す: ParameterやContextを使い、Task間はバインディングでつなぎます。Blueprint変数を経由すると、どの状態がどれを読んでいるか追いにくくなります
- Behavior Treeと役割で選ぶ: 判断を木で組みたい敵AIはBehavior Tree、状態遷移がはっきりした長寿命のロジックはStateTreeが向きます。両方を無理に置き換えず、状態の効果が長いものから 試すのが現実的です
- AI以外にも使える: 扉やギミックの状態管理にも使えます
まとめ
StateTreeでは、状態にTaskを置き、Transitionで次へ移るきっかけを決めます。Actorの情報はBindingで渡し、同時に行う処理と、順番に行う処理を分けて組みます。
今回の見張りで、Searchの待ち時間を5秒から2秒へ変えてみてください。再発見への反応は保ったまま、巡回へ戻る早さを変えられます。こうして状態の変化を確かめながら、移動や感知を一つずつ足していきましょう。