ue-ai-navigation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE AI and Navigation
UE AI与导航系统
You are an expert in Unreal Engine's AI and navigation systems.
你是Unreal Engine AI与导航系统领域的专家。
Context
上下文信息
Read for project AI plugins, subsystem configs, enabled modules (AIModule, NavigationSystem, GameplayStateTreeModule, SmartObjectsModule), and existing AI frameworks.
.agents/ue-project-context.md请阅读,了解项目AI插件、子系统配置、已启用模块(AIModule、NavigationSystem、GameplayStateTreeModule、SmartObjectsModule)以及现有AI框架。
.agents/ue-project-context.mdInformation 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复杂度、导航需求(地面/飞行/水下、动态障碍物、流加载)、所需的感知类型、Behavior Tree与State Tree的偏好选择、多人游戏权限模型,以及用于预算规划的Agent数量。
AI Architecture
AI架构
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: , ,
AIModuleNavigationSystemGameplayTasksAPawn
└── 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模块:, ,
AIModuleNavigationSystemGameplayTasksAIController
AIController
cpp
// MyAIController.h
UCLASS()
class AMyAIController : public AAIController
{
GENERATED_BODY()
public:
AMyAIController();
UPROPERTY(EditDefaultsOnly, Category = AI)
TObjectPtr<UBehaviorTree> 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);
}cpp
// MyAIController.h
UCLASS()
class AMyAIController : public AAIController
{
GENERATED_BODY()
public:
AMyAIController();
UPROPERTY(EditDefaultsOnly, Category = AI)
TObjectPtr<UBehaviorTree> 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
AAIController核心API
cpp
// Navigation
EPathFollowingRequestResult::Type MoveToActor(AActor* Goal, float AcceptanceRadius = -1,
bool bStopOnOverlap = true, bool bUsePathfinding = true, bool bCanStrafe = true,
TSubclassOf<UNavigationQueryFilter> 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<UNavigationQueryFilter> 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;cpp
// Navigation
EPathFollowingRequestResult::Type MoveToActor(AActor* Goal, float AcceptanceRadius = -1,
bool bStopOnOverlap = true, bool bUsePathfinding = true, bool bCanStrafe = true,
TSubclassOf<UNavigationQueryFilter> 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<UNavigationQueryFilter> 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)在Pawn中设置:
AIControllerClass = AMyAIController::StaticClass(); AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;Blackboard
Blackboard(黑板)
| Type | Get | Set |
|---|---|---|
| Object | | |
| Vector | | |
| Bool | | |
| Float | | |
| Int | | |
| Enum | | |
| Name | | |
| Rotator | | |
| String | | |
| Class | | |
cpp
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<UBlackboardKeyType_Bool> 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 (squad-wide alerts via propagation).
UBlackboardDataUAISystem| 类型 | 获取方法 | 设置方法 |
|---|---|---|
| Object | | |
| Vector | | |
| Bool | | |
| Float | | |
| Int | | |
| Enum | | |
| Name | | |
| Rotator | | |
| String | | |
| Class | | |
cpp
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);
// 高性能缓存访问器(避免重复名称查找):
FBBKeyCachedAccessor<UBlackboardKeyType_Bool> BBInCombat;
// 初始化: BBInCombat = FBBKeyCachedAccessor<...>(*BBComp, KeyID);
// 使用: bool b = BBInCombat.Get(); BBInCombat.SetValue(*BB, true);将标记为Instance Synced的键,可在所有使用同一的AI之间共享值(通过传播实现小队范围内的警报)。
UBlackboardDataUAISystemBehavior Tree Nodes
Behavior Tree(行为树)节点
Custom Task
自定义任务节点
cpp
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<AActor>(
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()
}cpp
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<AActor>(
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
自定义装饰器节点
cpp
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<AActor>(
OwnerComp.GetBlackboardComponent()->GetValueAsObject(TargetKey.SelectedKeyName));
return IsValid(Target) && OwnerComp.GetAIOwner()->LineOfSightTo(Target);
}
};cpp
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<AActor>(
OwnerComp.GetBlackboardComponent()->GetValueAsObject(TargetKey.SelectedKeyName));
return IsValid(Target) && OwnerComp.GetAIOwner()->LineOfSightTo(Target);
}
};Custom Service
自定义服务节点
cpp
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<AActor*> 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 < BestDist) { BestDist = D; Best = A; }
}
OwnerComp.GetBlackboardComponent()->SetValueAsObject(TEXT("TargetActor"), Best);
}
};cpp
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<AActor*> 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 < BestDist) { BestDist = D; Best = A; }
}
OwnerComp.GetBlackboardComponent()->SetValueAsObject(TEXT("TargetActor"), Best);
}
};Built-in Nodes (reference)
内置节点(参考)
Tasks: , , , , , , , , , ,
BTTask_MoveToBTTask_MoveDirectlyTowardBTTask_WaitBTTask_WaitBlackboardTimeBTTask_RunEQSQueryBTTask_PlayAnimationBTTask_MakeNoiseBTTask_RotateToFaceBBEntryBTTask_RunBehaviorBTTask_RunBehaviorDynamicBTTask_FinishWithResultDecorators: , , , , , , , , ,
BTDecorator_BlackboardBTDecorator_CompareBBEntriesBTDecorator_CooldownBTDecorator_TagCooldownBTDecorator_LoopBTDecorator_TimeLimitBTDecorator_DoesPathExistBTDecorator_IsAtLocationBTDecorator_CheckGameplayTagsOnActorBTDecorator_ForceSuccessComposites: (first success), (first failure), (main task + background subtree; main task drives completion). UE does not ship a general-purpose Parallel node — is the built-in option. For true parallel execution of N branches, implement a custom or chain multiple nodes.
SelectorSequenceSimpleParallelSimpleParallelUBTCompositeNodeSimpleParallel任务节点:, , , , , , , , , ,
BTTask_MoveToBTTask_MoveDirectlyTowardBTTask_WaitBTTask_WaitBlackboardTimeBTTask_RunEQSQueryBTTask_PlayAnimationBTTask_MakeNoiseBTTask_RotateToFaceBBEntryBTTask_RunBehaviorBTTask_RunBehaviorDynamicBTTask_FinishWithResult装饰器节点:, , , , , , , , ,
BTDecorator_BlackboardBTDecorator_CompareBBEntriesBTDecorator_CooldownBTDecorator_TagCooldownBTDecorator_LoopBTDecorator_TimeLimitBTDecorator_DoesPathExistBTDecorator_IsAtLocationBTDecorator_CheckGameplayTagsOnActorBTDecorator_ForceSuccess组合节点:(首个成功节点), (首个失败节点), (主任务+后台子树;主任务决定完成状态)。UE未提供通用Parallel节点——是内置的可选方案。若要实现N个分支的真正并行执行,需自定义或链式组合多个节点。
SelectorSequenceSimpleParallelSimpleParallelUBTCompositeNodeSimpleParallelAI Perception
AI感知
cpp
// In AIController constructor:
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AISenseConfig_Hearing.h"
#include "Perception/AISenseConfig_Damage.h"
UAIPerceptionComponent* AIP = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerception"));
SetPerceptionComponent(*AIP);
UAISenseConfig_Sight* Sight = CreateDefaultSubobject<UAISenseConfig_Sight>(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<UAISenseConfig_Hearing>(TEXT("Hearing"));
Hearing->HearingRange = 3000.f;
Hearing->DetectionByAffiliation.bDetectEnemies = true;
Hearing->DetectionByAffiliation.bDetectNeutrals = true;
AIP->ConfigureSense(*Hearing);cpp
// 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);cpp
// On perceived actors — add UAIPerceptionStimuliSourceComponent:
#include "Perception/AIPerceptionStimuliSourceComponent.h"
UAIPerceptionStimuliSourceComponent* Source =
CreateDefaultSubobject<UAIPerceptionStimuliSourceComponent>(TEXT("StimuliSource"));
Source->bAutoRegister = true;
Source->RegisterForSense(TSubclassOf<UAISense>(UAISense_Sight::StaticClass()));
Source->RegisterForSense(TSubclassOf<UAISense>(UAISense_Hearing::StaticClass()));cpp
// Query perception state:
UAIPerceptionComponent* PC = GetAIPerceptionComponent();
TArray<AActor*> Visible; PC->GetCurrentlyPerceivedActors(UAISense_Sight::StaticClass(), Visible);
TArray<AActor*> Hostiles; PC->GetPerceivedHostileActors(Hostiles);
bool bCanSee = PC->HasActiveStimulus(*Target, UAISense::GetSenseID<UAISense_Sight>());
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<AActor*>& UpdatedActors)
// Also: OnTargetPerceptionForgotten, OnTargetPerceptionInfoUpdatedAdditional sense configs: (fires on physical contact with perceived actor), (propagates enemy awareness across teammates via ), (predicts future location — call ).
UAISenseConfig_TouchUAISense_TeamIGenericTeamAgentInterfaceUAISense_PredictionUAISense_Prediction::RequestPawnPredictionEventcpp
// In AIController constructor:
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AISenseConfig_Hearing.h"
#include "Perception/AISenseConfig_Damage.h"
UAIPerceptionComponent* AIP = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerception"));
SetPerceptionComponent(*AIP);
UAISenseConfig_Sight* Sight = CreateDefaultSubobject<UAISenseConfig_Sight>(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<UAISenseConfig_Hearing>(TEXT("Hearing"));
Hearing->HearingRange = 3000.f;
Hearing->DetectionByAffiliation.bDetectEnemies = true;
Hearing->DetectionByAffiliation.bDetectNeutrals = true;
AIP->ConfigureSense(*Hearing);cpp
// 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);cpp
// On perceived actors — add UAIPerceptionStimuliSourceComponent:
#include "Perception/AIPerceptionStimuliSourceComponent.h"
UAIPerceptionStimuliSourceComponent* Source =
CreateDefaultSubobject<UAIPerceptionStimuliSourceComponent>(TEXT("StimuliSource"));
Source->bAutoRegister = true;
Source->RegisterForSense(TSubclassOf<UAISense>(UAISense_Sight::StaticClass()));
Source->RegisterForSense(TSubclassOf<UAISense>(UAISense_Hearing::StaticClass()));cpp
// Query perception state:
UAIPerceptionComponent* PC = GetAIPerceptionComponent();
TArray<AActor*> Visible; PC->GetCurrentlyPerceivedActors(UAISense_Sight::StaticClass(), Visible);
TArray<AActor*> Hostiles; PC->GetPerceivedHostileActors(Hostiles);
bool bCanSee = PC->HasActiveStimulus(*Target, UAISense::GetSenseID<UAISense_Sight>());
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<AActor*>& UpdatedActors)
// Also: OnTargetPerceptionForgotten, OnTargetPerceptionInfoUpdated其他感知配置:(与感知对象发生物理接触时触发)、(通过在队友间传播敌方感知信息)、(预测未来位置——调用)。
UAISenseConfig_TouchUAISense_TeamIGenericTeamAgentInterfaceUAISense_PredictionUAISense_Prediction::RequestPawnPredictionEventNavigation System
导航系统
cpp
#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<FNavPathPoint>& 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<UNavModifierComponent>(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<ARecastNavMesh>(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 in-level, or implement for traversal callbacks.
ANavLinkProxyINavLinkCustomInterfacecpp
#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<FNavPathPoint>& 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<UNavModifierComponent>(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<ARecastNavMesh>(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:可在关卡中使用,或实现以处理遍历回调。
ANavLinkProxyINavLinkCustomInterfaceEQS (Environment Query System)
EQS(环境查询系统)
cpp
#include "EnvironmentQuery/EnvQueryManager.h"
UPROPERTY(EditDefaultsOnly) TObjectPtr<UEnvQuery> FindCoverQuery;
void AMyAIController::RunCoverQuery()
{
FEnvQueryRequest Request(FindCoverQuery, this);
Request.Execute(EEnvQueryRunMode::SingleResult,
FQueryFinishedSignature::CreateUObject(this, &AMyAIController::OnCoverDone));
}
void AMyAIController::OnCoverDone(TSharedPtr<FEnvQueryResult> Result)
{
if (Result.IsValid() && Result->IsSuccessful())
GetBlackboardComponent()->SetValueAsVector(TEXT("CoverLocation"),
Result->GetItemAsLocation(0));
}Run modes: (cheapest), , ,
SingleResultRandomBest5PctRandomBest25PctAllMatchingBTTask_RunEQSQuery: set , , . Async lifecycle managed via .
EQSRequest.QueryTemplateBlackboardKeyRunModeFBTEnvQueryTaskMemory.RequestIDCustom context (resolve BB actor for use in tests):
cpp
UCLASS()
class UEnvQueryContext_Enemy : public UEnvQueryContext
{
GENERATED_BODY()
virtual void ProvideContext(FEnvQueryInstance& QI, FEnvQueryContextData& CD) const override
{
AAIController* C = Cast<AAIController>(Cast<APawn>(QI.Owner.Get())->GetController());
AActor* Enemy = Cast<AActor>(C->GetBlackboardComponent()->GetValueAsObject(TEXT("TargetActor")));
if (IsValid(Enemy)) UEnvQueryItemType_Actor::SetContextHelper(CD, Enemy);
}
};See for generator and test configurations.
references/eqs-reference.mdcpp
#include "EnvironmentQuery/EnvQueryManager.h"
UPROPERTY(EditDefaultsOnly) TObjectPtr<UEnvQuery> FindCoverQuery;
void AMyAIController::RunCoverQuery()
{
FEnvQueryRequest Request(FindCoverQuery, this);
Request.Execute(EEnvQueryRunMode::SingleResult,
FQueryFinishedSignature::CreateUObject(this, &AMyAIController::OnCoverDone));
}
void AMyAIController::OnCoverDone(TSharedPtr<FEnvQueryResult> Result)
{
if (Result.IsValid() && Result->IsSuccessful())
GetBlackboardComponent()->SetValueAsVector(TEXT("CoverLocation"),
Result->GetItemAsLocation(0));
}运行模式:(性能开销最低)、、、
SingleResultRandomBest5PctRandomBest25PctAllMatchingBTTask_RunEQSQuery:设置、、。异步生命周期由管理。
EQSRequest.QueryTemplateBlackboardKeyRunModeFBTEnvQueryTaskMemory.RequestID自定义上下文(解析黑板中的Actor以供测试使用):
cpp
UCLASS()
class UEnvQueryContext_Enemy : public UEnvQueryContext
{
GENERATED_BODY()
virtual void ProvideContext(FEnvQueryInstance& QI, FEnvQueryContextData& CD) const override
{
AAIController* C = Cast<AAIController>(Cast<APawn>(QI.Owner.Get())->GetController());
AActor* Enemy = Cast<AActor>(C->GetBlackboardComponent()->GetValueAsObject(TEXT("TargetActor")));
if (IsValid(Enemy)) UEnvQueryItemType_Actor::SetContextHelper(CD, Enemy);
}
};生成器及测试配置请参考。
references/eqs-reference.mdState Trees (UE 5.1+)
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.
cpp
// Build.cs: "GameplayStateTreeModule"
#include "Components/StateTreeComponent.h"
UCLASS()
class AMyNPC : public ACharacter
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere) TObjectPtr<UStateTreeComponent> StateTreeComp;
};
AMyNPC::AMyNPC() { StateTreeComp = CreateDefaultSubobject<UStateTreeComponent>(TEXT("StateTree")); }
void AMyNPC::BeginPlay() { Super::BeginPlay(); StateTreeComp->StartLogic(); }State Tree tasks use + . Override , , .
FStateTreeTaskBaseFInstanceDataTypeEnterStateTickExitStateState Tree evaluators () run persistently across all active states — use them for shared context data (nearest enemy, threat level) that multiple states read, analogous to BT services.
FStateTreeEvaluatorBaseState Trees适用于实现更简洁的状态机、设计师友好的工作流以及与Smart Objects的集成。Behavior Tree则更适合实现带有优先级中断的复杂反应式战斗逻辑。
cpp
// Build.cs: "GameplayStateTreeModule"
#include "Components/StateTreeComponent.h"
UCLASS()
class AMyNPC : public ACharacter
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere) TObjectPtr<UStateTreeComponent> StateTreeComp;
};
AMyNPC::AMyNPC() { StateTreeComp = CreateDefaultSubobject<UStateTreeComponent>(TEXT("StateTree")); }
void AMyNPC::BeginPlay() { Super::BeginPlay(); StateTreeComp->StartLogic(); }State Tree任务使用 + 。需重写、、方法。
FStateTreeTaskBaseFInstanceDataTypeEnterStateTickExitStateState Tree评估器()会在所有活动状态中持续运行——可用于存储多个状态都需要读取的共享上下文数据(如最近的敌人、威胁等级),功能类似于BT的服务节点。
FStateTreeEvaluatorBaseSmart Objects
Smart Objects(智能对象)
Add to world actors (set ). AI interacts via :
USmartObjectComponentSmartObjectDefinitionUSmartObjectSubsystemcpp
// Build.cs: "SmartObjectsModule"
#include "SmartObjectSubsystem.h"
USmartObjectSubsystem* SOS = USmartObjectSubsystem::GetCurrent(GetWorld());
FSmartObjectRequestFilter Filter;
FSmartObjectRequest Req(FBox(Origin, Origin).ExpandBy(500.f), Filter);
FSmartObjectRequestResult Res = SOS->FindSmartObject(Req);
if (Res.IsValid())
{
FSmartObjectClaimHandle Handle = SOS->MarkSlotAsClaimed(Res.SlotHandle, ESmartObjectClaimPriority::Normal);
// ... use slot, then:
SOS->MarkSlotAsFree(Handle);
}为世界中的Actor添加(设置)。AI可通过与之交互:
USmartObjectComponentSmartObjectDefinitionUSmartObjectSubsystemcpp
// Build.cs: "SmartObjectsModule"
#include "SmartObjectSubsystem.h"
USmartObjectSubsystem* SOS = USmartObjectSubsystem::GetCurrent(GetWorld());
FSmartObjectRequestFilter Filter;
FSmartObjectRequest Req(FBox(Origin, Origin).ExpandBy(500.f), Filter);
FSmartObjectRequestResult Res = SOS->FindSmartObject(Req);
if (Res.IsValid())
{
FSmartObjectClaimHandle Handle = SOS->MarkSlotAsClaimed(Res.SlotHandle, ESmartObjectClaimPriority::Normal);
// ... use slot, then:
SOS->MarkSlotAsFree(Handle);
}Common Mistakes
常见错误
Polling instead of event-driven: Do not check per-frame in if a observer abort or achieves the same result.
TickTaskBTDecorator_BlackboardWaitForMessageOvercomplicated BTs: Flatten needless nesting. Use services for periodic knowledge updates at . Gate expensive EQS with .
Interval >= 0.5sBTDecorator_CooldownNavMesh gaps: Always place . For streaming/procedural levels, call after generation or use on AI pawns.
ANavMeshBoundsVolumeNavSys->Build()UNavigationInvokerComponentServer vs client: only exists on the server. Replicate AI state via Pawn replicated properties, not through the controller. BT Blackboard is not replicated.
AAIControllerLarge worlds: Use hierarchical NavMesh (RecastNavMesh actor settings). Set EQS max parallel queries per frame in Project Settings > AI > EQS.
轮询而非事件驱动:如果通过观察者中断或可实现相同效果,请勿在中逐帧检查。
BTDecorator_BlackboardWaitForMessageTickTask过度复杂的BT结构:简化不必要的嵌套结构。使用服务节点进行周期性知识更新时,设置。通过来限制高开销EQS查询的执行频率。
Interval >= 0.5sBTDecorator_CooldownNavMesh间隙:务必在关卡中放置。若无此对象,将无法生成NavMesh瓦片。对于流加载/开放世界场景,请在AI Pawn上使用替代。
ANavMeshBoundsVolumeUNavigationInvokerComponent服务器与客户端差异:仅存在于服务器端。请通过Pawn的复制属性来同步AI状态,而非通过Controller。BT的Blackboard数据不会被复制。
AAIController大型世界:使用分层NavMesh(RecastNavMesh Actor设置)。在项目设置>AI>EQS中设置每帧EQS最大并行查询数。
Edge Cases
边缘情况
- Flying/swimming: Set /
FNavAgentProperties::bCanFly; assign abCanSwimsubclass with matchingUNavAreaflags. CustomSupportedAgentssubclasses define traversal cost and area flags — create one per movement domain (e.g.,UNavAreawith high cost for ground agents, zero for swimmers). Apply via NavModifierVolumes or NavMesh generation settings.UNavArea_Water - Dedicated server: Sight perception uses collision traces, not rendering — works fine. Guard non-server code with .
HasAuthority() - AI with GAS: BT tasks request ability activation; abilities manage their own latent logic.
- 飞行/水下导航:设置/
FNavAgentProperties::bCanFly;分配带有匹配bCanSwim标记的SupportedAgents子类。自定义UNavArea子类可定义遍历成本和区域标记——可为每个移动域创建一个子类(例如,UNavArea对地面Agent设置高成本,对水下Agent设置零成本)。可通过NavModifierVolumes或NavMesh生成设置应用。UNavArea_Water - 专用服务器:视觉感知使用碰撞检测而非渲染——可正常工作。使用来保护非服务器端代码。
HasAuthority() - 集成GAS的AI:BT任务请求能力激活;能力自身管理其异步逻辑。
Related Skills
相关技能
- — component setup patterns for AI
ue-actor-component-architecture - — GameMode AI spawning, controller/pawn relationships
ue-gameplay-framework - — delegates, subsystems, UObject patterns
ue-cpp-foundations - — GAS + AI integration
ue-gameplay-abilities
- — AI组件设置模式
ue-actor-component-architecture - — GameMode AI生成、Controller/Pawn关系
ue-gameplay-framework - — 委托、子系统、UObject模式
ue-cpp-foundations - — GAS与AI的集成
ue-gameplay-abilities
Reference Files
参考文档
- — patrol, combat, flee, investigate, squad BT patterns
references/behavior-tree-patterns.md - — generator and test configurations for spatial queries
references/eqs-reference.md
- — 巡逻、战斗、逃跑、侦查、小队等行为树模式
references/behavior-tree-patterns.md - — 空间查询的生成器及测试配置
references/eqs-reference.md