自定义行为树服务
2023年10月31日 2024年1月11日
说明
AI Service / AIDecorator
BTService
添加到行为树节点的特殊类. 自带Tick函数, 在其中实现游戏逻辑.
在给定条件, 可以修改黑板中的变量值.
本节将通过服务重写定位敌人逻辑, 当敌人出现在NPC的捕捉范围内, 计算得到敌人附近一点, 使NPC跑去该位置.
可以添加到行为树的任意节点
添加黑板变量
BB_STUCharacter
添加类型为Object的变量, 命名为EnemyActor; 存放选中敌人
创建行为树服务类
- | |
---|---|
AI/Services | |
基类 | BTService |
Public | |
名称 | STUFindEnemyService |
每秒搜索距离NPC最近的敌人
修改头文件搜索路径
ShootThemUp: ShootThemUp.Build.cs
1PublicIncludePaths.AddRange(new string[] 2{ 3 "ShootThemUp/Public/Player", 4 "ShootThemUp/Public/Components", 5 "ShootThemUp/Public/Dev", 6 "ShootThemUp/Public/Weapon", 7 "ShootThemUp/Public/UI", 8 "ShootThemUp/Public/Animations", 9 "ShootThemUp/Public/Pickups", 10 "ShootThemUp/Public/Weapon/Components", 11 "ShootThemUp/Public/AI", 12 "ShootThemUp/Public/AI/Tasks", 13 "ShootThemUp/Public/AI/Services" 14});
添加构造函数
public
ShootThemUp: AI/Services/STUFindEnemyService.h
1USTUFindEnemyService();
设置服务名称
ShootThemUp: AI/Services/STUFindEnemyService.cpp
1USTUFindEnemyService::USTUFindEnemyService() 2{ 3 NodeName = "Find Enemy"; 4}
添加属性: 存放黑板变量信息
protected
ShootThemUp: AI/Services/STUFindEnemyService.h
1UPROPERTY(EditAnywhere, BlueprintReadWrite) 2FBlackboardKeySelector EnemyActorKey;
添加TickNode函数
可以设置调用频率; 在此定位敌人
protected
ShootThemUp: AI/Services/STUFindEnemyService.h
1virtual void TickNode(UBehaviorTreeComponent &OwnerComp, uint8 *NodeMemory, float DeltaSeconds) override;
ShootThemUp: AI/Services/STUFindEnemyService.cpp
1#include "BehaviorTree/BlackboardComponent.h" 2#include "AIController.h" 3#include "STUUtils.h" 4#include "Components/STUAIPerceptionComponent.h" 5 6void USTUFindEnemyService::TickNode(UBehaviorTreeComponent &OwnerComp, uint8 *NodeMemory, float DeltaSeconds) 7{ 8 const auto Blackboard = OwnerComp.GetBlackboardComponent(); 9 if (Blackboard) 10 { 11 const auto Controller = OwnerComp.GetAIOwner(); 12 const auto PerceptionComponent = STUUtils::GetSTUPlayerComponent<USTUAIPerceptionComponent>(Controller); 13 if (PerceptionComponent) 14 { 15 Blackboard->SetValueAsObject(EnemyActorKey.SelectedKeyName, PerceptionComponent->GetClosestEnemy()); 16 } 17 } 18 Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds); 19}
SetValueAsObject函数
第一个参数: 变量名
第二个参数: 给出指向敌人的UObject指针
在AIController中使用服务
添加属性: 存放变量名
无法使用选择器, Controller不涉及黑板变量逻辑
ShootThemUp: AI/STUAIController.h
1UPROPERTY(EditAnywhere, BlueprintReadWrite) 2FName FocusOnKeyName = "EnemyActor";
添加接口: 返回敌人对象
private
ShootThemUp: AI/STUAIController.h
1AActor *GetFocusOnActor() const;
ShootThemUp: AI/STUAIController.cpp
1#include "BehaviorTree/BlackboardComponent.h" 2 3AActor *ASTUAIController::GetFocusOnActor() const 4{ 5 if (!GetBlackboardComponent()) return nullptr; 6 return Cast<AActor>(GetBlackboardComponent()->GetValueAsObject(FocusOnKeyName)); 7}
在Tick函数中使用服务
不直接使用感知组件接口
ShootThemUp: AI/STUAIController.cpp
1// Tick 2const auto AimActor = GetFocusOnActor();
NPC行为树
BT_STUCharacter
NPC有两个职责:
- 在地图上巡逻
将序列命名为Random roam - 攻击
添加节点: Sequence, 命名为Attack
添加节点: Selector
从左到右运行子树. 若排行较前的子树返回Fail, 则执行下一子树
为Selector添加服务: 一直查找敌人
右键 Selector
, Add Service > STUFindEnemyService
设置STUFindEnemyService
- | 说明 | |
---|---|---|
EnemyActorKey | EnemyActor | |
Service > Interval | Tick调用频率上限F | |
Service > RandomDeviation | Tick调用频率活动范围FR |
Tick实际调用频率在F-FR ~ F+FR之间
为Attack添加任务: MoveTo
Blackboard Key无法选择EnemyActor
设置黑板变量EnemyActor
BB_STUCharacter > Enemy Actor > Blackboard Details > Key Type > Base Class
, 设置为Actor : UObject对象在世界中不具有变化属性
设置MoveTo目的地为EnemyActor; 勾选 Observe Blackboard Value
变量值发生改变时, 新目的地立即生效
查看
UE4.27: 当Find Enemy不为空, 且无法找到AimLocation时, 才会执行Attack序列
UE5.1: 定时执行Find Enemy, 从左到右执行Attack和Rand roam; Enemy Actor不为空, 靠近选中敌人, 为空, 去到随机生成的位置
使用Decorator
Decorator包含逻辑判断, 类似switch, 可以中断整棵子树
根据EnemyActor的设置情况, 决定执行哪个序列
为Attack添加Decorator: Blackboard
Blackboard会检查黑板变量的值
-
选中
Blackboard Based Condition
Details > Description > Node Name
命名为Has Enemy
Details > Blackboard > Blackboard Key
, 选中EnemyActorDetails > Blackboard > Key Query
, 选择IsSet分支Details > Flow Control > Observer aborts
, 选择Self -
若EnemyActor有值, 执行Attack, 否则中止序列
为Random roam添加Decorator
-
命名为No Enemy
Details > Blackboard > Blackboard Key
, 选中EnemyActorDetails > Blackboard > Key Query
, 选择IsNotSet分支Details > Flow Control > Observer aborts
, 选择Self -
若EnemyActor没有值, 执行Random roam, 否则中止序列
使NPC移动到敌人附近
- 添加标志位: 以自身为圆心寻找随机点
巡逻
; 以敌人为圆心寻找随机点攻击
protected
ShootThemUp: AI/Tasks/STUNextLocationTask.h
1UPROPERTY(EditAnywhere, BlueprintReadWrite) 2bool SelfCenter = true;
- 添加属性: 如果以敌人为圆心寻找随机点, 保存敌人信息
protected
ShootThemUp: AI/Tasks/STUNextLocationTask.h
1UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "!SelfCenter")) 2FBlackboardKeySelector CenterActorKey;
- 设置随机位置中心点
ShootThemUp: AI/Tasks/STUNextLocationTask.cpp
1// ExecuteTask 2 3auto Location = Pawn->GetActorLocation(); 4if (!SelfCenter) 5{ 6 auto CenterActor = Cast<AActor>(Blackboard->GetValueAsObject(CenterActorKey.SelectedKeyName)); 7 if (!CenterActor) return EBTNodeResult::Failed; 8 Location = CenterActor->GetActorLocation(); 9} 10 11const auto Found = NavSys->GetRandomReachablePointInRadius(Location, Radius, NavLocation);
设置Attack序列
-
添加任务: STUNextLocationTask
进行如下设置 -
设置MoveTo目的地