gamedev-skills/awesome-gamedev-agent-skillsPrüfung bestanden
SKILL DETAIL
unreal-behavior-trees
gamedev-skills/awesome-gamedev-agent-skills/unreal-behavior-trees
Build NPC AI in Unreal Engine 5 with Behavior Trees and Blackboards: composites (Selector/Sequence), tasks, decorators, services, and running the tree from an AIController. Use when creating enemy/NPC AI, BT_/BB_ assets, custom BTTask or BTService nodes, or when the user mentions Behavior Tree, Blackboard, AIController, BTTask, decorator, or service.
Installationen · 242Quelle ansehen
Installation
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill unreal-behavior-trees
Skill-Dateien
SKILL.md
Zuletzt synchronisiert · 25.10.2026
references/custom-bttask.md›
# Custom BTTask in C++ (UE 5.8)
Depth for `unreal-behavior-trees`: writing a custom Behavior Tree task node in C++. Verified
against the Behavior Trees documentation and the `UBTTaskNode` API. Requires the `AIModule`
dependency in `*.Build.cs`.
## Instant task (returns immediately)
```cpp
// BTTask_ClearTarget.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_ClearTarget.generated.h"
UCLASS()
class MYGAME_API UBTTask_ClearTarget : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_ClearTarget();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory) override;
// Exposes a Blackboard key picker in the BT editor.
UPROPERTY(EditAnywhere, Category = "Blackboard")
FBlackboardKeySelector TargetKey;
};
```
```cpp
// BTTask_ClearTarget.cpp
#include "BTTask_ClearTarget.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_ClearTarget::UBTTask_ClearTarget()
{
NodeName = "Clear Target";
}
EBTNodeResult::Type UBTTask_ClearTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory)
{
if (UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent())
{
BB->ClearValue(TargetKey.SelectedKeyName);
return EBTNodeResult::Succeeded; // done now
}
return EBTNodeResult::Failed;
}
```
`EBTNodeResult::Type` values: `Succeeded`, `Failed`, `Aborted`, `InProgress`.
## Latent task (finishes over time)
Return `InProgress`, then call `FinishLatentTask` when the work completes (e.g. a timer, an
async query, or a delegate callback). Tasks are **not instanced** by default: one node object is
shared by every AI running the tree, so per-run state (here the timer handle) goes in the
node's instance memory, never in member variables.
```cpp
// BTTask_WaitRandom.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_WaitRandom.generated.h"
struct FBTWaitRandomMemory
{
FTimerHandle TimerHandle;
};
UCLASS()
class MYGAME_API UBTTask_WaitRandom : public UBTTaskNode
{
GENERATED_BODY()
public:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory) override;
virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory) override;
virtual uint16 GetInstanceMemorySize() const override { return sizeof(FBTWaitRandomMemory); }
virtual void InitializeMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory,
EBTMemoryInit::Type InitType) const override
{
InitializeNodeMemory<FBTWaitRandomMemory>(NodeMemory, InitType);
}
virtual void CleanupMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory,
EBTMemoryClear::Type CleanupType) const override
{
CleanupNodeMemory<FBTWaitRandomMemory>(NodeMemory, CleanupType);
}
UPROPERTY(EditAnywhere, Category = "Wait") float MinTime = 1.f;
UPROPERTY(EditAnywhere, Category = "Wait") float MaxTime = 3.f;
};
```
```cpp
// BTTask_WaitRandom.cpp
#include "BTTask_WaitRandom.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "TimerManager.h"
EBTNodeResult::Type UBTTask_WaitRandom::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory)
{
UWorld* World = OwnerComp.GetWorld();
if (!World) return EBTNodeResult::Failed;
FBTWaitRandomMemory* Memory = CastInstanceNodeMemory<FBTWaitRandomMemory>(NodeMemory);
const float Delay = FMath::FRandRange(MinTime, MaxTime);
// Weak pointer: the component can be destroyed before the timer fires.
TWeakObjectPtr<UBehaviorTreeComponent> WeakComp(&OwnerComp);
World->GetTimerManager().SetTimer(Memory->TimerHandle,
FTimerDelegate::CreateWeakLambda(this, [this, WeakComp]()
{
if (UBehaviorTreeComponent* Comp = WeakComp.Get())
{
FinishLatentTask(*Comp, EBTNodeResult::Succeeded); // resumes the tree
}
}), Delay, /*bLoop*/ false);
return EBTNodeResult::InProgress; // tree waits on this node until FinishLatentTask
}
EBTNodeResult::Type UBTTask_WaitRandom::AbortTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory)
{
// A higher-priority branch took over: cancel the pending timer so it can't finish later.
if (UWorld* World = OwnerComp.GetWorld())
{
World->GetTimerManager().ClearTimer(
CastInstanceNodeMemory<FBTWaitRandomMemory>(NodeMemory)->TimerHandle);
}
return EBTNodeResult::Aborted;
}
```
`AbortTask` returns `Aborted` when it can stop immediately; return `InProgress` instead and call
`FinishLatentAbort(OwnerComp)` later if cancelling is itself asynchronous.
## Reading and writing typed Blackboard values
```cpp
UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
AActor* Target = Cast<AActor>(BB->GetValueAsObject(TargetKey.SelectedKeyName));
FVector Loc = BB->GetValueAsVector(TEXT("LastKnownLocation"));
BB->SetValueAsBool(TEXT("bIsInvestigating"), true);
```
## Notes
- Set `NodeName` in the constructor so the node reads clearly in the BT graph.
- `OwnerComp.GetAIOwner()` gives the `AAIController`; `GetAIOwner()->GetPawn()` the controlled
pawn — use these instead of caching world pointers.
- For nodes that need per-instance memory (counters, handles), override
`GetInstanceMemorySize`, construct and destroy the block with `InitializeNodeMemory<T>` /
`CleanupNodeMemory<T>`, and read it with `CastInstanceNodeMemory<T>` (as in the latent task
above). Member UPROPERTYs on a non-instanced node are shared across all uses; treat them as
read-only settings.
- A bare `FBlackboardKeySelector` offers every key in the editor and only `SelectedKeyName` is
usable until resolved. Deriving from `UBTTask_BlackboardBase` gives you a `BlackboardKey`
that is resolved in `InitializeFromAsset`; with your own selector, add a type filter in the
constructor (e.g. `AddObjectFilter`) and call `ResolveSelectedKey` in `InitializeFromAsset`.
- A custom **Service** subclasses `UBTService` and overrides `TickNode`; a custom **Decorator**
subclasses `UBTDecorator` and overrides `CalculateRawConditionValue`.
SKILL.md›
---
name: unreal-behavior-trees
description: >
Build NPC AI in Unreal Engine 5 with Behavior Trees and Blackboards: composites
(Selector/Sequence), tasks, decorators, services, and running the tree from an AIController.
Use when creating enemy/NPC AI, BT_/BB_ assets, custom BTTask or BTService nodes, or when
the user mentions Behavior Tree, Blackboard, AIController, BTTask, decorator, or service.
---
# Unreal Behavior Trees
Author NPC decision-making in UE5 with Behavior Trees driven by a Blackboard: structure the
tree with composites, gate branches with decorators, keep state current with services, and run
it from an AIController. Targets **UE 5.8**.
## When to use
- Use when building enemy/NPC AI: creating a `BT_`/`BB_` asset pair, structuring
Selector/Sequence branches, adding decorators (conditions) and services (periodic updates),
writing custom `BTTask`/`BTService` nodes, or wiring an AIController to run the tree.
- Use when the project has Behavior Tree (`BT_`) and Blackboard (`BB_`) assets and an
`AAIController`.
**When *not* to use:** the *concept* of AI (FSM vs BT vs steering, cross-engine) →
`game-ai`. Pure navigation/pathing math is engine navmesh (BT's `MoveTo` uses it). Simple
one-off logic may be cheaper as a small state machine than a full tree.
**Behavior Tree or StateTree?** UE 5.8 ships both, and Behavior Trees are not deprecated.
StateTree is Epic's general-purpose hierarchical state machine: it combines BT-style
selection with explicit States and Transitions. If the project already uses StateTree
assets, or the AI is naturally a set of modes with clear transitions (patrol → alert →
combat), check with the user before adding a Behavior Tree next to it. Keep using Behavior
Trees where the project already has `BT_`/`BB_` assets.
## Core workflow
1. **Create the pair:** a Blackboard (`BB_`) holds typed keys (the AI's memory: `TargetActor`,
`LastKnownLocation`, `bIsInvestigating`); a Behavior Tree (`BT_`) references that Blackboard.
2. **Possess and run.** An `AAIController` possesses the pawn and calls `RunBehaviorTree(BT)`,
which also initializes the referenced Blackboard.
3. **Structure with composites.** **Selector** runs children left→right until one *succeeds*
(priority/fallback: "attack, else chase, else patrol"). **Sequence** runs children until one
*fails* (do-all: "move to cover → reload → peek"). **Simple Parallel** runs one main task
alongside a secondary.
4. **Gate branches with Decorators** that read Blackboard keys (e.g. "Has Target?" guards the
combat branch). Set **Observer Aborts** so the tree re-evaluates when the key changes.
5. **Keep the Blackboard current with Services** attached to a branch — they tick periodically
(e.g. update `TargetActor` via a sight check) only while that branch is active.
6. **Do work in Tasks**, which return `Succeeded`, `Failed`, or `InProgress` (latent tasks like
`MoveTo` finish later).
7. **Verify** with the Behavior Tree debugger during PIE — it highlights the running node and
shows live Blackboard values, so you see exactly which branch executes.
## Patterns
### 1. AIController that runs the tree (C++)
```cpp
void AEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (BehaviorTree) // UPROPERTY(EditAnywhere) TObjectPtr<UBehaviorTree>
RunBehaviorTree(BehaviorTree); // initializes & uses the Blackboard the BT references
}
```
### 2. A priority tree (node structure)
```text
ROOT
└── Selector (try combat, else investigate, else patrol)
├── Sequence [Decorator: Blackboard 'TargetActor' Is Set, Observer Aborts: Both]
│ ├── Task: MoveTo (TargetActor) // latent: returns InProgress then Succeeded
│ └── Task: Attack
├── Sequence [Decorator: 'LastKnownLocation' Is Set]
│ ├── Task: MoveTo (LastKnownLocation)
│ └── Task: Wait (3s) + clear key
└── Task: Patrol (BTTask_FindPatrolPoint -> MoveTo)
```
`Observer Aborts: Both` makes the combat branch interrupt patrol the instant `TargetActor` is
set, and bail out when it's cleared — this is what makes the AI feel reactive.
### 3. Updating the Blackboard from code (e.g. on seeing the player)
```cpp
void AEnemyAIController::SetTarget(AActor* Target)
{
if (UBlackboardComponent* BB = GetBlackboardComponent())
BB->SetValueAsObject(TEXT("TargetActor"), Target); // key name must match the BB asset
}
// Clear with BB->ClearValue(TEXT("TargetActor")); to drop back to a lower-priority branch.
```
## Pitfalls
- **AI never starts** — the pawn isn't possessed (set the Pawn's *Auto Possess AI* to "Placed
in World or Spawned" and assign the AIController), or `RunBehaviorTree` was never called.
- **`MoveTo` instantly fails** — no NavMesh in the level (add a Nav Mesh Bounds Volume), or the
target is off the navmesh.
- **Branch doesn't react to changes** — the gating Decorator's **Observer Aborts** is set to
None; set it to Self/Lower Priority/Both so the tree re-evaluates when the key changes.
- **A task hangs the tree** — a custom task returned `InProgress` and never calls
`FinishLatentTask`. Always complete latent tasks.
- **AI behaves strangely only when several NPCs share a tree** — a custom task stores run state
in member variables. Task nodes are shared by every AI using the asset unless instanced; keep
per-AI state in node memory (see the reference).
- **Blackboard key typos** — `SetValueAsObject("Taget", ...)` silently does nothing; match the
key name and type exactly, or use a cached `FBlackboardKeySelector`.
- **Sequence vs Selector confusion** — Sequence = AND (stops on first failure); Selector = OR
(stops on first success). Swapping them inverts the behaviour.
## References
- For a **custom C++ `UBTTaskNode`** (instant and latent `ExecuteTask` returning `EBTNodeResult`,
per-AI node memory, `AbortTask`, and a `FBlackboardKeySelector`), read
`references/custom-bttask.md`.
- Primary docs: "Behavior Trees in Unreal Engine"
(`https://dev.epicgames.com/documentation/en-us/unreal-engine/behavior-trees-in-unreal-engine`).
## Related skills
- `game-ai` — engine-agnostic AI design (FSM, BT, steering, pathfinding choices).
- `unreal-cpp-gameplay` — the AIController and pawn classes in C++.
- `fps-shooter` / `tower-defense` — genres that compose enemy AI.