Retour aux skills
gamedev-skills/awesome-gamedev-agent-skillsContrôle réussi

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.

Installations · 242Voir la source

Installation

npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill unreal-behavior-trees

Fichiers du skill

SKILL.md

Dernière synchronisation · 25 oct. 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.