State machines vs. Behavior Trees, Unreal's BT architecture, and how to frame the tradeoff in an interview context.
The interview question is "State machines vs. behavior trees — when and why?" The answer is not which is better — it is which fits the problem.
The real deciding factor is often who needs to author it and what they need to control:
Unreal's Behavior Tree system evaluates a tree of nodes top-to-bottom, left-to-right each tick, selecting the highest-priority branch whose conditions are satisfied.
| Node type | Role | Example |
|---|---|---|
| Composite | Controls flow between children | Selector, Sequence, Simple Parallel |
| Task | A leaf node that does work | MoveTo, Wait, PlayAnimation, Attack |
| Decorator | A condition on any node — gates or aborts | Blackboard check, Cooldown, Is In Range |
| Service | Runs on a tick interval while its branch is active | Update nearest enemy, refresh EQS query |
Root
└── Selector
├── Sequence [HasTarget?] ↠Decorator gate
│ ├── Service: UpdateTargetLocation
│ ├── MoveTo(TargetLocation)
│ └── Task: MeleeAttack
├── Sequence [HeardSound?]
│ ├── MoveTo(SoundLocation)
│ └── Task: Investigate
└── Task: Patrol
When a decorator condition changes mid-execution, abort types control what happens:
| Abort type | Behavior |
|---|---|
| None | No abort — condition is only checked when the node is first entered |
| Self | Aborts the subtree this decorator is on if the condition becomes false |
| Lower Priority | Aborts any lower-priority branch if this condition becomes true |
| Both | Aborts self if condition goes false; aborts lower priority if it goes true |
// Custom task — subclass UBTTask_BlueprintBase in BP,
// or UBTTaskNode in C++
UCLASS()
class UBTTask_MeleeAttack : public UBTTaskNode
{
GENERATED_BODY()
virtual EBTNodeResult::Type ExecuteTask(
UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory) override;
};
The Blackboard is the AI's shared memory — a key-value store that the Behavior Tree reads and writes through decorators, services, and tasks. It decouples the AI's data from its logic.
| Key | Type | Used by |
|---|---|---|
| TargetActor | Object (AActor) | MoveTo, MeleeAttack, decorators |
| TargetLocation | Vector | MoveTo patrol waypoints |
| LastKnownPlayerLoc | Vector | Investigation behavior |
| bIsAlert | Bool | Decorator gates for combat vs idle |
| CurrentHealth | Float | Retreat threshold decorators |
// From C++ (e.g. in a Service or AIController)
UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
BB->SetValueAsObject(TEXT("TargetActor"), PerceivedActor);
BB->SetValueAsVector(TEXT("TargetLocation"), TargetActor->GetActorLocation());
The AAIController is the bridge between the Behavior Tree and the Pawn. It possesses an AI Pawn, runs the BT, owns the Blackboard, and owns the AI Perception component.
UCLASS()
class AEnemyAIController : public AAIController
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly)
UBehaviorTree* BehaviorTree;
virtual void OnPossess(APawn* InPawn) override
{
Super::OnPossess(InPawn);
RunBehaviorTree(BehaviorTree);
}
};
The UAIPerceptionComponent handles sight and hearing — it fires delegates when the AI sees or hears something, and the AIController updates the Blackboard in response.
// Bind to perception events in AIController
PerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(
this, &AEnemyAIController::OnPerceptionUpdated
);
void AEnemyAIController::OnPerceptionUpdated(
AActor* Actor, FAIStimulus Stimulus)
{
if (Stimulus.WasSuccessfullySensed())
{
GetBlackboardComponent()->SetValueAsObject(
TEXT("TargetActor"), Actor);
}
}
EQS lets AI ask spatial questions of the world — "What is the best cover position?", "Where can I flank the player?", "Which patrol point is closest and not visible to the player?" — and get back a scored set of candidates.
The most common pattern: a BT Service runs an EQS query on a timer and writes the best result to the Blackboard. The Behavior Tree then uses that location for movement tasks — no per-frame spatial logic in C++ required.