ue-ai-navigation
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 ab
By quodsoler · 811 installs
npx skills add quodsoler/unreal-engine-skills --skill ue-ai-navigation
Source repository · Upstream listing
UE AI and Navigation
Target engine: UE 5.8 . APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use".
Classic UE AI runs on a server only AAIController that owns a brain component ( UBehaviorTreeComponent ), a UBlackboardComponent , a UAIPerceptionComponent and a UPathFollowingComponent . Behavior trees, perception and EQS live in the AIModule ; navmesh generation and queries live in NavigationSystem ; BT tasks that wrap gameplay tasks need GameplayTasks . Smart Objects ( SmartObjectsModule , plugin SmartObjects ), ZoneGraph ( ZoneGraph , Experimental in 5.8), MassAI (Experimental in 5.8) and NavCorridor (Experimental in 5.8) are opt in plugins. Add what you use to PublicDependencyModuleNames in your .Build.cs , e.g. "AIModule", "NavigationSystem", "GameplayTasks" .
Context
Read .agents/ue project context.md if it exists (module names, conventions, enabled plugins, GAS/networking setup). Do not stop if it is missing.
Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code.
Request is about… Go to
Possessing a pawn, running a tree, move requests, focus [AI Controller]( ai controller)
Reading/writing AI knowledge, key selectors, observers [Blackboard]( blackboard)
Custom tasks, decorators, services, node memory [Behavior Tree Nodes]( behavior tree nodes)
Sight/hearing/damage senses, stimuli sources [AI Perception]( ai perception)
Path queries, projection, random points, invokers [Navigation and Pathfinding]( navigation and pathfinding)
Area costs, query filters, jump/off mesh links [Nav Areas, Filters and Off Mesh Links]( nav areas filters and off mesh links)
Cover/flank/patrol point selection, custom contexts [EQS Environment Query System]( eqs environment query system)
State Tree instead of a behavior tree [State Tree for AI]( state tree for ai)
Interactable world objects, slot claiming [Smart Objects]( smart objects)
Lane based crowds, large agent counts [ZoneGraph, MassAI and NavCorridor]( zonegraph massai and navcorridor)
AI Controller
Need Use
One liner move with per call options MoveToActor / MoveToLocation — returns EPathFollowingRequestResult::{Failed, AlreadyAtGoal, RequestSuccessful}
Full control (goal actor + radius + filter + partial path) and the resulting path MoveTo(FAIMoveRequest, FNavPathSharedPtr ) — returns FPathFollowingRequestResult with MoveId and Code
Move a controller that has no BT/blackboard, e.g. a click to move test UAIBlueprintHelperLibrary::SimpleMoveToLocation(AController , const FVector&)
React to arrival Override OnMoveCompleted , or bind the dynamic delegate ReceiveMoveCompleted ( FAIRequestID , EPathFollowingResult::Type )
Build an FAIMoveRequest with its setters: SetGoalActor , SetGoalLocation , SetAcceptanceRadius , SetUsePathfinding , SetAllowPartialPath , SetNavigationFilter , SetProjectGoalLocation , SetCanStrafe , SetReachTestIncludesAgentRadius .
On the 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 (returns uint8 ) SetValueAsEnum (takes uint8 )
Name / String / Class / Rotator GetValueAsName / …String / …Class / …Rotator SetValueAs… matching
All accessors take const FName& KeyName . ClearValue(FName) and IsVectorValueSet(FName) have FBlackboard::FKey overloads that skip the name lookup.
Return EBlackboardNotificationResult::ContinueObserving to stay registered, RemoveObserver to unsubscribe. UnregisterObserversFrom(this) drops every observer an object registered.
In BT nodes, never hard code key names. Expose a FBlackboardKeySelector UPROPERTY and read SelectedKeyName ; the tree resolves it against the asset through FBlackboardKeySelector::ResolveSelectedKey(const UBlackboardData&) , and GetSelectedKeyID() gives the cached FBlackboard::FKey . Mark a key Instance Synced ( FBlackboardEntry::bInstanceSynced in the UBlackboardData asset) to share its value across every AI using that asset — squad wide alerts without extra plumbing; UBlackboardData::HasSynchronizedKeys() reports whether any key is synced. For hot paths, cache the key: FBBKeyCachedAccessor<UBlackboardKeyType Bool built from (const UBlackboardComponent&, FBlackboard::FKey) exposes Get() and SetValue(UBlackboardComponent&, Value) and skips the per call name lookup.
Behavior Tree Nodes
Base class Override Signature source
UBTTaskNode ExecuteTask , AbortTask , TickTask , OnTaskFinished , OnMessage BehaviorTree/BTTaskNode.h
UBTDecorator CalculateRawConditionValue (const), OnNodeActivation , OnNodeDeactivation , OnNodeProcessed BehaviorTree/BTDecorator.h
UBTService TickNode , OnSearchStart , OnBecomeRelevant , OnCeaseRelevant BehaviorTree/BTService.h (relevance hooks inherited from BTAuxiliaryNode.h )
Any node GetInstanceMemorySize , InitializeMemory , CleanupMemory BehaviorTree/BTNode.h
Copy these verbatim (only the class name changes):
EBTNodeResult::Type has exactly four values: Succeeded , Failed , Aborted , InProgress . There is no FBTNodeResult .
Custom task with node memory
Per AI runtime state goes in a plain struct (for example FMyAttackTaskMemory { float ElapsedTime; TWeakObjectPtr<AActor CachedTarget; } ), never in UPROPERTY members of the node. The task returns sizeof of that struct from GetInstanceMemorySize() . The full UMyBTTask Attack header and .cpp ( FBlackboardKeySelector key, ExecuteTask / TickTask / AbortTask ) are in [behavior tree patterns](references/behavior tree patterns.md custom task with node memory full source).
Node memory rules. NodeMemory is a raw byte block sized by GetInstanceMemorySize() and laid out per BT instance, so it is the only safe place for per AI runtime state on a shared node object. Access it with CastInstanceNodeMemory<T () , which asserts sizeof(T) <= GetInstanceMemorySize() . For non trivial members override InitializeMemory / CleanupMemory and call the helpers InitializeNodeMemory<T (NodeMemory, InitType) / CleanupNodeMemory<T (NodeMemory, CleanupType) ; they placement new and destroy correctly. Tasks that opt into tick intervals get a FBTTaskMemory header ( NextTickRemainingTime , AccumulatedDeltaTime ) reachable through GetSpecialNodeMemory<FBTTaskMemory () ; the engine places it just before NodeMemory ( BTNode.h:390 ), so it never overlaps your struct.
Call INIT TASK NODE NOTIFY FLAGS() / INIT DECORATOR NODE NOTIFY FLAGS() / INIT SERVICE NODE NOTIFY FLAGS() in the constructor: they set bNotifyTick , bNotifyTaskFinished , bNotifyActivation and friends from which virtuals you actually overrode. Without them (or setting the bNotify flags by hand) task TickTask / OnTaskFinished and decorator/service activation and relevance overrides are never called ( BTTaskNode.cpp:12 13 ); only UBTService defaults bNotifyTick and bNotifyOnSearch to true ( BTService.cpp:11 12 ). Returning InProgress means the task is latent — finish it with FinishLatentTask(OwnerComp, Result) (or FinishLatentAbort(OwnerComp) from an abort). To wait on an external event instead of ticking, call WaitForMessage(OwnerComp, FName) and finish inside OnMessage ; senders use FAIMessage::Send(Pawn, FAIMessage(TEXT("MontageCompleted"), Sender, true)) or UAIBlueprintHelperLibrary::SendAIMessage .
To force a re evaluation from outside the tree, use UBehaviorTreeComponent::RequestExecution(const UBTCompositeNode RequestedOn, int32 InstanceIdx, const UBTNode RequestedBy, int32 RequestedByChildIndex, EBTNodeResult::Type ContinueWithResult, bool bStoreForDebugger = true) ; from a decorator prefer ConditionalFlowAbort(OwnerComp, EBTDecoratorAbortRequest::ConditionResultChanged) and let FlowAbortMode decide the scope.
Built in nodes worth reusing before writing your own: tasks UBTTask MoveTo , UBTTask MoveDirectlyToward , UBTTask Wait , UBTTask WaitBlackboardTime , UBTTask RunEQSQuery , UBTTask PlayAnimation , UBTTask PlaySound , UBTTask MakeNoise , UBTTask RotateToFaceBBEntry , UBTTask RunBehavior , UBTTask RunBehaviorDynamic , UBTTask SetKeyValueBool / UBTTask SetKeyValueFloat / UBTTask SetKeyValueObject (one per key type), UBTTask FinishWithResult ; decorators UBTDecorator Blackboard , UBTDecorator CompareBBEntries , UBTDecorator Cooldown , UBTDecorator TagCooldown , UBTDecorator Loop , UBTDecorator LoopUntil , UBTDecorator TimeLimit , UBTDecorator DoesPathExist , UBTDecorator IsAtLocation , UBTDecorator ReachedMoveGoal , UBTDecorator CheckGameplayTagsOnActor , UBTDecorator ConeCheck , UBTDecorator ForceSuccess ; services UBTService DefaultFocus , UBTService RunEQS . Composites ship as UBTComposite Selector , UBTComposite Sequence and UBTComposite SimpleParallel only — there is no general purpose parallel composite.
Tree topologies, a full decorator and service, abort modes and message driven tasks: [behavior tree patterns](references/behavior tree patterns.md).
AI Perception
Changing sense config at runtime takes effect only after RequestStimuliListenerUpdate() .
Delegate Signature Fires
OnTargetPerceptionUpdated (AActor Actor, FAIStimulus Stimulus) once per processed stimulus that changed state, i.e. per actor per sense ( AIPerceptionComponent.cpp:593 )
OnTargetPerceptionInfoUpdated (const FActorPerceptionUpdateInfo& UpdateInfo) same, but carries TargetId so it still fires for destroyed/null actors
OnTargetPerceptionForgotten (AActor Actor) after ForgetActor / ForgetAll , or when all stimuli expire — age out only with UAISystem::bForgetStaleActors enabled ( AISystem.h:84 )
OnPerceptionUpdated (const TArray<AActor & UpdatedActors) once per stimuli processing pass that updated anything, batched
FAIStimulus carries Strength , StimulusLocation , ReceiverLocation , Tag , Type (an FAISenseID ), plus WasSuccessfullySensed() , IsActive() and GetAge() . Branch per sense with Stimulus.Type == UAISense::GetSenseID<UAISense Sight () . Queries: GetCurrentlyPerceivedActors(TSubclassOf<UAISense SenseToUse, TArray<AActor & OutActors) , GetPerceivedHostileActors(TArray<AActor &) , GetPerceivedHostileActorsBySense(TSubclassOf<UAISense , TArray<AActor &) , HasActiveStimulus(const AActor&, FAISenseID) , ForgetActor(AActor ) , ForgetAll() .
Events are pushed, not polled: UAISense Hearing::ReportNoiseEvent(WorldContextObject, NoiseLocation, Loudness, Instigator, MaxRange, Tag) and UAISense Damage::ReportDamageEvent(WorldContextObject, DamagedActor, Instigator, DamageAmount, EventLocation, HitLocation, Tag) . Sight has no report function — it is trace driven and only sees registered sources. UAISense Prediction::RequestPawnPredictionEvent(APawn Requestor, AActor PredictedActor, float PredictionTime) asks where a target will be.
Make an actor perceivable either by adding UAIPerceptionStimuliSourceComponent and setting bAutoRegisterAsSource = true (or calling RegisterWithPerceptionSystem() ) plus RegisterForSense(UAISense Sight::StaticClass()) per sense, or by calling UAIPerceptionSystem::GetCurrent(World) RegisterSource( Actor) (all instantiated senses) / RegisterSourceForSenseClass(UAISense Sight::StaticClass(), Actor) — both take AActor& ( Perception/AIPerceptionSystem.h:139 141 ).
Other sense configs: UAISenseConfig Touch , UAISenseCon