Smart Enemy AI
January 22, 2026About 5 min
Smart Enemy AI
Behavior Tree
Basic Enemy Behavior

AIMoveTo节点在C++中的实现:
UAIAsyncTaskBlueprintProxy* UAIBlueprintHelperLibrary::CreateMoveToProxyObject(...)
在场景中添加导航网格,然后按P显示:

上面的逻辑替换为BehaviorTree:
黑板设置:

在AIController中初始化行为树,设置黑板数值。
void ABaseAIController::InitDefaultBT()
{
RunBehaviorTree(DefaultBT);
ACharacter* TargetPlayer = UGameplayStatics::GetPlayerCharacter(this, 0);
// 使用字符串对黑板中的对象进行绑定
Blackboard->SetValueAsObject("AttackTarget", TargetPlayer);
}
Custom Task Node
EBTNodeResult::Type UBTTask_DefaultAttack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
AAIController* const MyController = OwnerComp.GetAIOwner();
EBTNodeResult::Type Result = EBTNodeResult::Failed;
if (MyController->GetPawn()->Implements<UEnemyInterface>())
{
IEnemyInterface::Execute_Attack(MyController->GetPawn());
Result = EBTNodeResult::Succeeded;
}
return Result;
}

添加 自定义节点自己控制结束

常实现方法:
- 激活:virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
- 退出:virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
- 轮询:virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
Blackboard Selector
头文件:
UCLASS()
class SMARTENEMYAI_API UBTTask_FocusTarget : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_FocusTarget(const FObjectInitializer& ObjectInitialize);
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void InitializeFromAsset(UBehaviorTree& Asset) override;
protected:
/** blackboard key selector */
UPROPERTY(EditAnywhere, Category = Blackboard)
struct FBlackboardKeySelector AttackTargetKey;
};
实现:
#include "AI/Task/BTTask_FocusTarget.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_FocusTarget::UBTTask_FocusTarget(const FObjectInitializer& ObjectInitialize)
{
NodeName = "Focus Target";
// 设置过滤
AttackTargetKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UBTTask_FocusTarget, AttackTargetKey), AActor::StaticClass());
}
EBTNodeResult::Type UBTTask_FocusTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = EBTNodeResult::Failed;
AAIController* const MyController = OwnerComp.GetAIOwner();
UBlackboardComponent* Blackboard = OwnerComp.GetBlackboardComponent();
if (!MyController || !Blackboard || !AttackTargetKey.IsSet())
{
return Result;
}
if (AActor* Target = Cast<AActor>(Blackboard->GetValueAsObject(AttackTargetKey.SelectedKeyName)))
{
MyController->SetFocus(Target);
Result = EBTNodeResult::Succeeded;
}
return Result;
}
void UBTTask_FocusTarget::InitializeFromAsset(UBehaviorTree& Asset)
{
Super::InitializeFromAsset(Asset);
// 从资产中解析选中的键名
if (const UBlackboardData* BBAsset = GetBlackboardAsset())
{
AttackTargetKey.ResolveSelectedKey(*BBAsset);
}
else
{
AttackTargetKey.InvalidateResolvedKey();
}
}
Set Focus
在使用AAIController::SetFocus后,想要角色跟着旋转需要在角色上开启以下选项:

Custom Decorator
提前判断任务是否可以执行的节点

主要实现方法:
- virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
最终的行为树:

Patrolling & States
Spline
创建一个Spline Actor,使用AAIController::MoveTo功能逐个移动到Spline中的点去。新写一个行为树任务点UBTTask_MoveAlongPatrolRoute
Use Acceieration for Paths
AI在导航时,使用MoveTo方法是直接设置的速度,这样AI在移动时会有速度的突变。可以看下图角色到达一个点后速度立马归零,会造成一个顿挫感。

可以在敌人的CharacterMovementComponent中设置Use Acceieration for Paths减轻这种顿挫感。


C++ Enum In Blackboard
How can i use my c++ enum in my blackboard?
C++中定义了枚举:
UENUM(BlueprintType)
enum class EAIState : uint8
{
Passive,
Attacking,
Frozen,
Investigating,
Dead
};
在设置黑板枚举类型时,并不能选择到定义的枚举

这里需要在Enum Name输入需要使用的枚举名字即可

State Tree
本节的行为树:

Perception
Debug Key
'(单引号键)位置:键盘 Enter 左边 / L 右边(美式键盘)
命令:
ai.debug.

Debug说明:
- 绿色夹角:PeripheralVisionHalfAngle
- 绿色圈:SightRadius
- 粉色圈:LoseSightRadius
- 黄色圈:HearingRange
| 颜色 | 含义 |
|---|---|
| 🟢 绿色 | 当前正在被感知(看到) |
| 🟡 黄色 | 记忆中(之前看到,现在没看到) |
| 🔴 红色 | 丢失/无效目标 |
🧠 1️⃣ AISenseConfig_Sight(视觉)
| 参数名 | 类型 | 作用 | 说明 / 使用建议 |
|---|---|---|---|
| SightRadius | float | 视野半径 | AI 能看到的最大距离 |
| LoseSightRadius | float | 丢失视野半径 | 超过这个距离才真正“看不见”,通常 > SightRadius |
| PeripheralVisionHalfAngle | float | 视野角度 | 水平视角(例如 90° / 120°) |
| DetectionByAffiliation | struct | 阵营过滤 | 是否检测敌人/友军/中立 |
| AutoSuccessRangeFromLastSeenLocation | float | 自动成功范围 | 在最后看到位置附近,直接判定“仍可见” |
| MaxAge | float | 记忆时间 | 多久后忘记目标(秒) |
| PointOfViewBackwardOffset | float | 视角后移 | 让视线从角色后一点发出(减少遮挡问题) |
| NearClippingRadius | float | 近裁剪半径 | 太近的目标是否忽略 |
| SetMaxAge() | 函数 | 设置 MaxAge | Blueprint/C++ 都可设置 |
| StartsEnabled | bool | 是否默认启用 | 运行时是否自动生效 |
👉 核心重点:
- SightRadius + LoseSightRadius = “看见 vs 丢失”缓冲机制
- PeripheralVisionHalfAngle = 是否容易“侧面看到人”
👂 2️⃣ AISenseConfig_Hearing(听觉)
| 参数名 | 类型 | 作用 | 说明 |
|---|---|---|---|
| HearingRange | float | 听觉范围 | 能听到声音的最大距离 |
| LoSHearingRange | float | 视线听觉范围 | 有直线视线时的更远听觉范围 |
| DetectionByAffiliation | struct | 阵营过滤 | 同 Sight |
| MaxAge | float | 声音记忆时间 | 多久后忘记声音 |
| StartsEnabled | bool | 是否启用 | 默认是否生效 |
👉 注意:
- Hearing 必须配合:
UAISense_Hearing::ReportNoiseEvent(...)

💥 3️⃣ AISenseConfig_Damage(伤害感知)
| 参数名 | 类型 | 作用 | 说明 |
|---|---|---|---|
| MaxAge | float | 记忆时间 | 被攻击后记多久 |
| StartsEnabled | bool | 是否启用 | 默认开启 |
👉 特点:
- 不依赖距离
- 直接由 Damage 事件触发


✋ 4️⃣ AISenseConfig_Touch(接触)
| 参数名 | 类型 | 作用 | 说明 |
|---|---|---|---|
| MaxAge | float | 记忆时间 | 接触信息保留时间 |
| StartsEnabled | bool | 是否启用 | 默认启用 |
👉 一般用于:
- 物理碰撞触发 AI 反应
🧬 5️⃣ AISenseConfig_Team(阵营感知)
| 参数名 | 类型 | 作用 | 说明 |
|---|---|---|---|
| MaxAge | float | 记忆时间 | 阵营变化记忆 |
| StartsEnabled | bool | 是否启用 | 默认启用 |
👉 使用场景:
- 阵营切换(敌变友)
🧩 6️⃣ DetectionByAffiliation(所有 Sense 通用子结构)
| 参数名 | 类型 | 作用 | 说明 |
|---|---|---|---|
| bDetectEnemies | bool | 检测敌人 | 必开(大多数情况) |
| bDetectFriendlies | bool | 检测友军 | 视需求 |
| bDetectNeutrals | bool | 检测中立 | 常用于玩家 |
👉 ⚠️ 常见坑:
不勾选 = 完全检测不到!
🧠 7️⃣ AIPerceptionComponent 相关(补充关键)
虽然你问的是 Config,但这几个必须一起理解:
| 参数名 | 类型 | 作用 | 说明 |
|---|---|---|---|
| SensesConfig | array | 感知配置数组 | 所有 Sense 的集合 |
| DominantSense | class | 主导感知 | 决定最终位置来源 |
| OnPerceptionUpdated | delegate | 感知更新回调 | 多目标更新 |
| OnTargetPerceptionUpdated | delegate | 单目标更新 | 推荐使用 |
| bAutoRegisterAllPawnsAsSources | bool | 自动注册 Pawn | 一般关闭手动控制 |
🧱 8️⃣ Stimuli Source(必须配合)
AI 能“看到/听到”的对象必须有:
| 参数名 | 类型 | 作用 | 说明 |
|---|---|---|---|
| RegisterAsSourceForSenses | array | 注册感知类型 | 比如 Sight / Hearing |
| AutoRegisterAsSource | bool | 自动注册 | 一般开启 |
组件:
👉 AIPerceptionStimuliSourceComponent
🧠 总结一层逻辑(帮你彻底理清)
你可以这样理解:
Senses Config = 感官规则(能不能感知 + 怎么感知)
Stimuli Source = 被感知对象(有没有“信号”)
Perception Component = 中枢(收集 + 分发)