AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Ue Ai Navigation

skill-quodsoler-unreal-engine-skills-ue-ai-navigation · by quodsoler

Use this skill when implementing AI, AIController, behavior tree, blackboard, AI perception, NavMesh, EQS, navigation, pathfinding, State Tree, or Smart Objects in Unreal Engine. See references/behavior-tree-patterns.md for BT patterns and references/eqs-reference.md for EQS configuration. For AI ability use, see ue-gameplay-abilities.

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-quodsoler-unreal-engine-skills-ue-ai-navigation

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-quodsoler-unreal-engine-skills-ue-ai-navigation)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
6mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Ue Ai Navigation? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

UE AI and Navigation

You are an expert in Unreal Engine's AI and navigation systems.

Context

Read .agents/ue-project-context.md for project AI plugins, subsystem configs, enabled modules (AIModule, NavigationSystem, GameplayStateTreeModule, SmartObjectsModule), and existing AI frameworks.

Information Gathering

Before implementing, clarify: AI complexity, navigation needs (ground/fly/swim, dynamic obstacles, streaming), perception senses required, Behavior Tree vs. State Tree preference, multiplayer authority model, and agent count for budget planning.


AI Architecture

APawn
  └── AAIController (server-only in multiplayer)
        ├── UBehaviorTreeComponent  (UBrainComponent subclass)
        │     └── UBehaviorTree asset → UBlackboardData
        ├── UBlackboardComponent    (AI knowledge store)
        ├── UAIPerceptionComponent  (sight, hearing, damage)
        └── UPathFollowingComponent (NavMesh path execution)

Build.cs modules: AIModule, NavigationSystem, GameplayTasks


AIController

// MyAIController.h
UCLASS()
class AMyAIController : public AAIController
{
    GENERATED_BODY()
public:
    AMyAIController();
    UPROPERTY(EditDefaultsOnly, Category = AI)
    TObjectPtr BehaviorTreeAsset;
protected:
    virtual void OnPossess(APawn* InPawn) override;
    UFUNCTION()
    void OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);
};

// MyAIController.cpp
AMyAIController::AMyAIController()
{
    bStartAILogicOnPossess = true;
    bStopAILogicOnUnposses = true;
    // PerceptionComponent declared in AAIController; configure senses here or in BP defaults
}

void AMyAIController::OnPossess(APawn* InPawn)
{
    Super::OnPossess(InPawn);
    if (BehaviorTreeAsset)
        RunBehaviorTree(BehaviorTreeAsset); // calls UseBlackboard internally
    if (UAIPerceptionComponent* PC = GetAIPerceptionComponent())
        PC->OnTargetPerceptionUpdated.AddDynamic(this, &AMyAIController::OnTargetPerceptionUpdated);
}

Key AAIController API

// Navigation
EPathFollowingRequestResult::Type MoveToActor(AActor* Goal, float AcceptanceRadius = -1,
    bool bStopOnOverlap = true, bool bUsePathfinding = true, bool bCanStrafe = true,
    TSubclassOf FilterClass = {}, bool bAllowPartialPath = true);

EPathFollowingRequestResult::Type MoveToLocation(const FVector& Dest, float AcceptanceRadius = -1,
    bool bStopOnOverlap = true, bool bUsePathfinding = true,
    bool bProjectDestinationToNavigation = false, bool bCanStrafe = true,
    TSubclassOf FilterClass = {}, bool bAllowPartialPath = true);

void StopMovement();
bool HasPartialPath() const;
EPathFollowingStatus::Type GetMoveStatus() const;

// Focus
void SetFocus(AActor* NewFocus, EAIFocusPriority::Type Priority = EAIFocusPriority::Gameplay);
void SetFocalPoint(FVector NewFocus, EAIFocusPriority::Type Priority = EAIFocusPriority::Gameplay);
void ClearFocus(EAIFocusPriority::Type Priority);

// Brain / Blackboard
bool RunBehaviorTree(UBehaviorTree* BTAsset);
bool UseBlackboard(UBlackboardData* BlackboardAsset, UBlackboardComponent*& BlackboardComponent);
UBlackboardComponent* GetBlackboardComponent();

// Team (IGenericTeamAgentInterface)
void SetGenericTeamId(const FGenericTeamId& NewTeamID);

// Delegate: FAIMoveCompletedSignature ReceiveMoveCompleted (RequestID, Result)

On Pawn: AIControllerClass = AMyAIController::StaticClass(); AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;


Blackboard

| Type | Get | Set | |------|-----|-----| | Object | GetValueAsObject | SetValueAsObject | | Vector | GetValueAsVector | SetValueAsVector | | Bool | GetValueAsBool | SetValueAsBool | | Float | GetValueAsFloat | SetValueAsFloat | | Int | GetValueAsInt | SetValueAsInt | | Enum | GetValueAsEnum | SetValueAsEnum | | Name | GetValueAsName | SetValueAsName | | Rotator | GetValueAsRotator | SetValueAsRotator | | String | GetValueAsString | SetValueAsString | | Class | GetValueAsClass | SetValueAsClass |

UBlackboardComponent* BB = GetBlackboardComponent();
BB->SetValueAsObject(TEXT("TargetActor"), SomeActor);
BB->SetValueAsVector(TEXT("LastKnownLocation"), Location);
BB->ClearValue(TEXT("TargetActor"));
bool bSet = BB->IsVectorValueSet(TEXT("PatrolLocation"));

// Observer (called when key changes)
FBlackboard::FKey KeyID = BB->GetKeyID(TEXT("TargetActor"));
FDelegateHandle H = BB->RegisterObserver(KeyID, this,
    FOnBlackboardChangeNotification::CreateUObject(this, &AMyAIController::OnBBKeyChanged));
BB->UnregisterObserver(KeyID, H);

// High-perf cached accessor (avoids repeated name lookups):
FBBKeyCachedAccessor BBInCombat;
// Init: BBInCombat = FBBKeyCachedAccessor(*BBComp, KeyID);
// Use: bool b = BBInCombat.Get(); BBInCombat.SetValue(*BB, true);

Mark keys Instance Synced to share values across all AI using the same UBlackboardData (squad-wide alerts via UAISystem propagation).


Behavior Tree Nodes

Custom Task

UCLASS()
class UMyBTTask_Attack : public UBTTaskNode
{
    GENERATED_BODY()
public:
    UMyBTTask_Attack() { NodeName = TEXT("Attack"); INIT_TASK_NODE_NOTIFY_FLAGS(); }
    UPROPERTY(EditAnywhere) FBlackboardKeySelector TargetKey;
protected:
    virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
    virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
    virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};

EBTNodeResult::Type UMyBTTask_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
    AActor* Target = Cast(
        OwnerComp.GetBlackboardComponent()->GetValueAsObject(TargetKey.SelectedKeyName));
    if (!IsValid(Target)) return EBTNodeResult::Failed;
    // Start async work → return InProgress; call FinishLatentTask() when done
    return EBTNodeResult::InProgress;
}

void UMyBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded); // or Failed
}

EBTNodeResult::Type UMyBTTask_Attack::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
    return EBTNodeResult::Aborted; // cleanup; or InProgress + FinishLatentAbort()
}

Custom Decorator

UCLASS()
class UMyBTDecorator_CanSee : public UBTDecorator
{
    GENERATED_BODY()
public:
    UMyBTDecorator_CanSee()
    {
        INIT_DECORATOR_NODE_NOTIFY_FLAGS();
        bAllowAbortLowerPri = true; bAllowAbortChildNodes = true;
        FlowAbortMode = EBTFlowAbortMode::Both;
    }
    UPROPERTY(EditAnywhere) FBlackboardKeySelector TargetKey;
protected:
    virtual bool CalculateRawConditionValue(
        UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override
    {
        AActor* Target = Cast(
            OwnerComp.GetBlackboardComponent()->GetValueAsObject(TargetKey.SelectedKeyName));
        return IsValid(Target) && OwnerComp.GetAIOwner()->LineOfSightTo(Target);
    }
};

Custom Service

UCLASS()
class UMyBTService_UpdateTarget : public UBTService
{
    GENERATED_BODY()
public:
    UMyBTService_UpdateTarget() { Interval = 0.5f; RandomDeviation = 0.1f; INIT_SERVICE_NODE_NOTIFY_FLAGS(); }
protected:
    virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override
    {
        TArray Hostiles;
        OwnerComp.GetAIOwner()->GetAIPerceptionComponent()->GetPerceivedHostileActors(Hostiles);
        AActor* Best = nullptr; float BestDist = FLT_MAX;
        FVector MyLoc = OwnerComp.GetAIOwner()->GetPawn()->GetActorLocation();
        for (AActor* A : Hostiles)
        {
            float D = FVector::Dist(MyLoc, A->GetActorLocation());
            if (D SetValueAsObject(TEXT("TargetActor"), Best);
    }
};

Built-in Nodes (reference)

Tasks: BTTask_MoveTo, BTTask_MoveDirectlyToward, BTTask_Wait, BTTask_WaitBlackboardTime, BTTask_RunEQSQuery, BTTask_PlayAnimation, BTTask_MakeNoise, BTTask_RotateToFaceBBEntry, BTTask_RunBehavior, BTTask_RunBehaviorDynamic, BTTask_FinishWithResult

Decorators: BTDecorator_Blackboard, BTDecorator_CompareBBEntries, BTDecorator_Cooldown, BTDecorator_TagCooldown, BTDecorator_Loop, BTDecorator_TimeLimit, BTDecorator_DoesPathExist, BTDecorator_IsAtLocation, BTDecorator_CheckGameplayTagsOnActor, BTDecorator_ForceSuccess

Composites: Selector (first success), Sequence (first failure), SimpleParallel (main task + background subtree; main task drives completion). UE does not ship a general-purpose Parallel node — SimpleParallel is the built-in option. For true parallel execution of N branches, implement a custom UBTCompositeNode or chain multiple SimpleParallel nodes.


AI Perception

// In AIController constructor:
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AISenseConfig_Hearing.h"
#include "Perception/AISenseConfig_Damage.h"

UAIPerceptionComponent* AIP = CreateDefaultSubobject(TEXT("AIPerception"));
SetPerceptionComponent(*AIP);

UAISenseConfig_Sight* Sight = CreateDefaultSubobject(TEXT("Sight"));
Sight->SightRadius = 2000.f;
Sight->LoseSightRadius = 2500.f;
Sight->PeripheralVisionAngleDegrees = 60.f;
Sight->AutoSuccessRangeFromLastSeenLocation = 400.f;
Sight->DetectionByAffiliation.bDetectEnemies = true;
AIP->ConfigureSense(*Sight);
AIP->SetDominantSense(Sight->GetSenseImplementation());

UAISenseConfig_Hearing* Hearing = CreateDefaultSubobject(TEXT("Hearing"));
Hearing->HearingRange = 3000.f;
Hearing->DetectionByAffiliation.bDetectEnemies = true;
Hearing->DetectionByAffiliation.bDetectNeutrals = true;
AIP->ConfigureSense(*Hearing);
// Perception handler:
void AMyAIController::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
    UBlackboardComponent* BB = GetBlackboardComponent();
    if (Stimulus.WasSuccessfullySensed())
    {
        BB->SetValueAsObject(TEXT("TargetActor"), Actor);
        BB->SetValueAsVector(TEXT("LastKnownLocation"), Stimulus.StimulusLocation);
    }
    else
    {
        // Lost target — keep last known location for investigation
        BB->SetValueAsVector(TEXT("LastKnownLocation"), Stimulus.StimulusLocation);
    }
}

// Report noise manually (e.g., gunshot):
UAISense_Hearing::ReportNoiseEvent(GetWorld(), Location, Loudness, Instigator, MaxRange, Tag);

// Report damage:
UAISense_Damage::ReportDamageEvent(GetWorld(), DamagedActor, Instigator, Amount, EventLoc, HitLoc);
// On perceived actors — add UAIPerceptionStimuliSourceComponent:
#include "Perception/AIPerceptionStimuliSourceComponent.h"
UAIPerceptionStimuliSourceComponent* Source =
    CreateDefaultSubobject(TEXT("StimuliSource"));
Source->bAutoRegister = true;
Source->RegisterForSense(TSubclassOf(UAISense_Sight::StaticClass()));
Source->RegisterForSense(TSubclassOf(UAISense_Hearing::StaticClass()));
// Query perception state:
UAIPerceptionComponent* PC = GetAIPerceptionComponent();
TArray Visible; PC->GetCurrentlyPerceivedActors(UAISense_Sight::StaticClass(), Visible);
TArray Hostiles; PC->GetPerceivedHostileActors(Hostiles);
bool bCanSee = PC->HasActiveStimulus(*Target, UAISense::GetSenseID());
PC->ForgetActor(Target);
PC->ForgetAll();
// Per-actor delegate (shown above):
// OnTargetPerceptionUpdated — fires once per actor whose perception state changed
// Batch delegate — fires once per frame with all updated actors:
// OnPerceptionUpdated — signature: void(const TArray& UpdatedActors)
// Also: OnTargetPerceptionForgotten, OnTargetPerceptionInfoUpdated

Additional sense configs: UAISenseConfig_Touch (fires on physical contact with perceived actor), UAISense_Team (propagates enemy awareness across teammates via IGenericTeamAgentInterface), UAISense_Prediction (predicts future location — call UAISense_Prediction::RequestPawnPredictionEvent).


Navigation System

#include "NavigationSystem.h"
UNavigationSystemV1* NavSys = UNavigationSystemV1::GetCurrent(GetWorld());

// Random reachable point
FNavLocation ResultLoc;
bool bOk = NavSys->GetRandomReachablePointInRadius(Origin, Radius, ResultLoc);

// Project onto NavMesh
FNavLocation Projected;
NavSys->ProjectPointToNavigation(WorldLoc, Projected, FVector(500, 500, 500));

// Sync path query
FPathFindingQuery Query; Query.StartLocation = Start; Query.EndLocation = End;
FPathFindingResult Result = NavSys->FindPathSync(Query);
if (Result.IsSuccessful()) { TArray& Pts = Result.Path->GetPathPoints(); }

// Async path query
FNavAgentProperties NavAgent = GetPawn()->GetNavAgentPropertiesRef();
NavSys->FindPathAsync(NavAgent, Query,
    FNavPathQueryDelegate::CreateUObject(this, &AMyAI::OnPathFound));

// Dynamic obstacle on actor:
#include "NavModifierComponent.h"
UNavModifierComponent* Mod = CreateDefaultSubobject(TEXT("NavMod"));
Mod->AreaClass = UNavArea_Obstacle::StaticClass();

// Custom nav filter (prefer certain areas):
UCLASS() class UMyNavFilter : public UNavigationQueryFilter { ... };
MoveToActor(Target, -1.f, true, true, true, UMyNavFilter::StaticClass());

// Runtime NavMesh rebuild (after procedural generation):
NavSys->Build();

// Access RecastNavMesh for agent config (mirrors Project Settings > Navigation)
ARecastNavMesh* RNM = Cast(NavSys->GetDefaultNavDataInstance());
// Key properties: AgentRadius, AgentHeight, AgentMaxStepHeight, AgentMaxSlope
// ANavMeshBoundsVolume must exist in level — without it, no tiles generate.
// For streaming/open-world: use UNavigationInvokerComponent on AI pawns instead.

Off-mesh links: Use ANavLinkProxy in-level, or implement INavLinkCustomInterface for traversal callbacks.


EQS (Environment Query System)

#include "EnvironmentQuery/EnvQueryManager.h"

UPROPERTY(EditDefaultsOnly) TObjectPtr FindCoverQuery;

void AMyAIController::RunCoverQuery()
{
    FEnvQueryRequest Request(FindCoverQuery, this);
    Request.Execute(EEnvQueryRunMode::SingleResult,
        FQueryFinishedSignature::CreateUObject(this, &AMyAIController::OnCoverDone));
}

void AMyAIController::OnCoverDone(TSharedPtr Result)
{
    if (Result.IsValid() && Result->IsSuccessful())
        GetBlackboardComponent()->SetValueAsVector(TEXT("CoverLocation"),
            Result->GetItemAsLocation(0));
}

Run modes: SingleResult (cheapest), RandomBest5Pct, RandomBest25Pct, AllMatching

BTTask_RunEQSQuery: set EQSRequest.QueryTemplate, BlackboardKey, RunMode. Async lifecycle managed via FBTEnvQueryTaskMemory.RequestID.

Custom context (resolve BB actor for use in tests):

UCLASS()
class UEnvQueryContext_Enemy : public UEnvQueryContext
{
    GENERATED_BODY()
    virtual void ProvideContext(FEnvQueryInstance& QI, FEnvQueryContextData& CD) const override
    {
        AAIController* C = Cast(Cast(QI.Owner.Get())->GetController());
        AActor* Enemy = Cast(C->GetBlackboardComponent()->GetValueAsObject(TEXT("TargetActor")));
        if (IsValid(Enemy)) UEnvQueryItemType_Actor::SetContextHelper(CD, Enemy);
    }
};

See references/eqs-reference.md for generator and test configurations.


State Trees (UE 5.1+)

Use State Trees for simpler state machines, designer-friendly workflows, and Smart Object integration. Use Behavior Trees for complex reactive combat with priority-based interrupts.

// Build.cs: "GameplayStateTreeModule"
#include "Components/StateTreeComponent.h"

UCLASS()
class AMyNPC : public ACharacter
{
    GENERATED_BODY()
    UPROPERTY(VisibleAnywhere) TObjectPtr StateTreeComp;
};
AMyNPC::AMyNPC() { StateTreeComp = CreateDefaultSubobject(TEXT("StateTree")); }
void AMyNPC::BeginPlay() { Super::BeginPlay(); StateTreeComp->StartLogic(); }

State Tree tasks use FStateTreeTaskBase + FInstanceDataType. Override EnterState, Tick, ExitState.

State Tree evaluators (FStateTreeEvaluatorBase) run persistently across

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.