斯坦福UE4 C++课学习补充22:AI行为树-寻路入门

server/2024/10/21 3:24:40/

文章目录

  • 一、创建敌对小兵
  • 二、寻路行为树
  • 三、会与角色保持距离的小兵
  • 四、更智能的小兵

一、创建敌对小兵

  1. 创建SAICharacter角色)和SAIController两个类(行为树),然后在UE中创建蓝图类。
  2. 寻路系统:用于为人工智能代理AI Agent提供寻路功能。
  • 为了帮助AI确定起点和终点之间的路径,引擎会根据场景中的碰撞体生成寻路网格体。这种简化的多边形网格体代表了关卡中的可寻路空间。默认情况下,寻路网格体会细分为多个区块,允许你重新构建寻路网格体的局部区域。
  • 生成的网格体由多个多边形组成,每个多边形都有一个"成本"值。搜索路径时,寻路算法会尝试找到总成本最低的路径。
  • 该系统还为代理提供了两种规避方法:相对速度障碍物Reciprocal Velocity Obstacles (RVO) 和 大规模人群绕行避让管理器Detour Crowd Manager

参考链接:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/navigation-system-in-unreal-engine?application_version=5.2

  1. 放置Actor中向世界添加导航网格体边界体积NavMeshBoundsVolume,并放大到覆盖地板面积。按P键可以显示导航范围。

二、寻路行为树

  1. 分别创建“人工智能”下的“黑板”和“行为树”,在黑板中创建向量类型的MoveToLocation键,用于记录小兵移动的目的地。

参考链接:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/behavior-trees-in-unreal-engine?application_version=5.2

  1. SAIController的相关代码中创建并行为树对象并暴露给UE,重写BeginPlay,实现运行行为树,并把玩家位置赋值给MoveToLocation
RunBehaviorTree(BehaviorTree);APawn* MyPawn = UGameplayStatics::GetPlayerPawn(this, 0);
if (MyPawn) {GetBlackboardComponent()->SetValueAsVector("MoveToLocation", MyPawn->GetActorLocation());}

UGameplayStatics是UE中一个静态类,提供了一些便捷的全局函数。参数0玩家索引,通常在单人游戏中,索引为0表示第一个玩家。
GetBlackboardComponent()函数常用于获取AI控制器的黑板组件。

  1. 将AIController的蓝图类命名为MinionControllerBP,将刚暴露的BehaviorTree设置为之前创建的寻路行为树;在小兵蓝图类中,将小兵自身中Pawn下的“AI控制器类”设置为MinionControllerBP

三、会与角色保持距离的小兵

  1. 行为树节点的三种返回状态:成功Success失败Failure)运行Running
  • 当一个节点返回运行中状态时,行为树会暂时中断,并在下一次更新时继续执行该节点。行为树的其他节点通常不会在当前节点处于运行中状态时被执行,直到当前节点返回成功或失败。
  1. 主要的行为树节点原型:
    (1)复合节点Composite
  • 复合节点用于定义行为树的结构和执行流程。它们可以包含多个子节点,并根据不同的规则来控制这些子节点的执行。
    序列节点Sequence:按顺序依次执行其子节点。它从第一个子节点开始执行,并且只有当当前子节点返回成功时,才会执行下一个子节点。如果某个子节点返回失败,序列节点立即返回失败,并停止执行后续节点。如果某个子节点返回运行中,序列节点也会返回运行中状态。
    选择节点Selector:会尝试按顺序执行其子节点,直到某个子节点返回成功或运行中。如果一个子节点返回成功,选择节点立即返回成功,并且不会执行后续子节点。如果所有子节点都返回失败,选择节点返回失败。
    并行节点Parallel:同时执行其所有子节点,并根据配置的规则决定何时返回成功或失败。通常的规则是,如果任何一个子节点返回失败,并行节点就返回失败;如果所有子节点都返回成功,则并行节点返回成功。
    (2)装饰器节点Decorator
  • 装饰节点用于对其他节点的执行进行控制或添加条件。它们通常包裹在任务或复合节点的外部,为它们增加额外的逻辑。
  • 装饰器节点在其所装饰的子节点即将执行时触发。它在每次子节点执行之前都会被评估,根据条件决定是否允许子节点执行或修改子节点的行为。
    黑板装饰器Blackboard: 根据黑板中某个键的值来决定是否执行其子节点。

举个例子:

  • 使用黑板装饰器来检查AI是否看到了敌人。如果读取到黑板中HasSeenEnemy键为True,则AI执行攻击行为;否则,不执行。

条件反转Inverter:会将其子节点的返回值反转。如果子节点返回成功,条件反转节点返回失败;如果子节点返回失败,条件反转节点返回成功。
循环节点Repeat:会重复执行其子节点,直到满足指定的条件(如循环次数、子节点返回特定状态等)

举个例子:

  • AI可能会不断地巡视一个区域,直到它看到敌人为止。循环节点可以让这个巡逻行为不断重复,直到某个条件触发。

(3)任务节点Task

  • 任务节点是行为树的叶子节点,它们执行具体的任务或动作。它们直接返回成功、失败或运行中状态。
  • 将组合和装饰器视为if语句和while循环以及用于定义代码流的其他语言构造,并将叶子节点视为特定于游戏的函数调用,这些调用实际上用于表达状态或者情况。
  • Leaf节点可以调用另一行为树,将现有树的数据上下文传递给被调用树。它可以对树进行大量模块化,以创建可以在无数地方重复使用的行为树,也可以使用上下文中的特定变量名进行操作。
    (4)服务节点Service:在行为树的执行过程中周期性地执行某些操作,通常用于更新黑板变量或执行一些需要不断检查的逻辑。
  • 与装饰器节点的不同:服务节点是在它们所在的子树(或者说它们所附加的复合节点)执行时周期性地执行。每次运行行为树时,服务节点都会被触发执行。
  • 与任务节点的不同:服务节点并不会返回成功、失败或运行中状态。它们是透明的,只是附加在行为树中的其他节点上来提供额外的逻辑支持。

举个例子:

  • 服务节点可以每隔一秒钟检测一次周围是否有敌人,并更新黑板中的“敌人位置”。这个更新操作是持续进行的,而不是在子节点执行之前才触发。
  1. 我们想实现的逻辑:当AI小兵进入人物的攻击范围后,就停止移动。因此我们需要定义攻击范围,以及实时获取小兵和玩家人物的距离。先新建名为SBTService_CheckAttackRange 服务节点
  2. 编译后遇到报错unresolved external symbol(模块缺失),记录解决方案如下:
    (1)编译报错显示缺少IGamePlayTaskOwnerInterface,于是在项目中搜索,注意把前缀I去掉,结果这个类是在Runtime\GameplayTasks文件夹下,那么缺失的模块名就叫GameplayTasks

我们平时所用到的模块大都会放在Source/Runtime的文件夹下

(2)打开源代码中的项目名.build.cs文件,添加我们所需要的模块。出于项目的规范统一,这里额外添加了AIModule,实际上不添加也是可以的,因为UE在.uproject已经为我们自动添加了。
在这里插入图片描述

  1. 重写SBTService_CheckAttackRange文件,重写的TickNode函数,并在此定义了FBlackboardKeySelector,并不使用硬编码,方便在编辑器中修改。
  2. TickNode函数:是服务节点在行为树执行期间每一帧都会被调用的函数。通过这个函数,服务节点可以执行任何需要周期性更新或检查的逻辑。
  • TickNode函数的调用频率可以自定义Tick Interval(即服务节点的检查间隔时间)。
  • 函数原型:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
  • 参数解析:
    ①行为树组件的引用
    ②一个指向服务节点内存的指针,用于在行为树执行过程中存储节点的临时数据。通过这个指针,服务节点可以在不同的TickNode调用之间保持状态信息。
    ③从上一次TickNode调用到这次调用之间的时间差(以秒为单位),用于帮助节点在帧间进行时间相关的计算或逻辑处理。
//SBTService_CheckAttackRange.h
class FPSPROJECT_API USBTService_CheckAttackRange : public UBTService
{GENERATED_BODY()
protected://可以动态的修改绑定的黑板键UPROPERTY(EditAnywhere, Category = "AI")FBlackboardKeySelector AttackRangeKey;virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;};//SBTService_CheckAttackRange.cpp
void USBTService_CheckAttackRange::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{Super::TickNode(OwnerComp,NodeMemory,DeltaSeconds);//Check distance between ai pawn and target actorUBlackboardComponent* BlackboardComp =  OwnerComp.GetBlackboardComponent();if (BlackboardComp){AActor* TargetActor = Cast<AActor>(BlackboardComp->GetValueAsObject("TargetActor"));if (TargetActor){AAIController* MyControllor =  OwnerComp.GetAIOwner();if (ensure(MyControllor)) {APawn* AIPawn = MyControllor->GetPawn();if (ensure(AIPawn)){float DistanceTo = FVector::Distance(TargetActor->GetActorLocation(), AIPawn->GetActorLocation());bool bWithinRange = DistanceTo < 2000.f;bool bHasLOS = false;if (bWithinRange){bHasLOS = MyControllor->LineOfSightTo(TargetActor);}BlackboardComp->SetValueAsBool(AttackRangeKey.SelectedKeyName, (bWithinRange && bHasLOS));}}}}
}
  1. 接下来的步骤:
    ①为了对应刚在代码里定义的AttackRangeKey,在黑板添加一个Bool类型(方便判断)的WithinAttackRange
    ②在行为树里添加一个Selector节点
    ③将刚才写的BTService挂到Selector节点上
    ④指定AttackRangeKey对应的黑板键
    ⑤在Sequence节点添加一个BlackBoard装饰器。
    ⑥装饰器细节面板:观察器中止为self,黑板键设置为WithInAttackRange,键查询设置为未设置。
    在这里插入图片描述
  2. 装饰器细节面板设置解析:
    (1)通知观察者Notify Observer:
  • 参数: 结果改变时OnResultChange、每次执行EveryTick等。
  • 作用: 当节点的执行结果发生变化时,是否通知观察者。在这种情况下,选择了“结果改变时”,意味着当该节点从成功变为失败或从失败变为成功时,观察者将被通知。
    (2)观察器中止Observer Aborts:
  • 参数: SelfLower PriorityNone
  • 作用: 决定了在满足特定条件时,行为树中哪些节点会被中止。
    Self: 如果这个节点的条件发生变化,它将中止自己。
    Lower Priority: 如果条件变化,它将中止优先级较低的子节点(自己不终止)。
    None: 不会中止任何节点。
    (3)对于黑板键是bool类型的变量,键查询未设置相当于当WithInAttackRangefalse的时候可以执行装饰器所装饰的节点
  1. 执行逻辑:
    Root开始进入SelectorCheckAttackRange服务节点开始检查工作,每隔一段时间执行TickNode。尝试执行下面Sequence第一个节点。
    ②若AI角色在攻击范围之外,黑板装饰器节点WithinAttackRangefalse即满足未设置,因此会执行Sequence节点下面的内容。
    ③当AI角色进入范围之内,装饰器发现WithinAttackRange变为true(由TickNode修改),立刻停止执行Sequence相关的节点,返回false
    false返回Selecto,继续执行后面的节点Wait
    Wait等待一秒,执行完毕后,返回Root节点,重复以上流程。

四、更智能的小兵

  1. 我们上面的逻辑只考虑了直线距离,那如果我们跟小兵之间有一堵墙,它仍然会停止然后对墙攻击,这显然不是我们想要的。增加以下逻辑即可:当射线检测LineOfSightTo到能看到在范围内时,赋值True
 //不想在超过范围外的地方也进行射线检测
bool bHasLOS = false;
if (bWithinRange){bHasLOS = MyControllor->LineOfSightTo(TargetActor);}
BlackboardComp->SetValueAsBool(AttackRangeKey.SelectedKeyName, (bWithinRange && bHasLOS));
  1. LineOfSightTo函数:用于执行射线检测(Line Trace)的功能,用于判断一个Actor是否能够直接看到另一个Actor
    (1)LineOfSightTo是在AController类中定义的一个函数,因此可以通过AI控制器AIController或玩家控制器PlayerController来调用。
    (2)函数原型:
bool LineOfSightTo(const AActor* Other, FVector ViewPoint, bool bAlternateChecks) const;

(3)参数解析:
①想要检查的目标Actor
②射线检测的起始点,默认选择控制器所控制的Pawn视点(通常是头部或眼睛的位置)。也指定一个具体的世界坐标来作为射线的起点。
③指定是否启用备用的检测机制。
④返回值:true表示有直接视线(即当前控制器的Pawn能够直接看到目标Actor),false表示没有直接视线(例如视线被墙壁、障碍物等阻挡)


http://www.ppmy.cn/server/103510.html

相关文章

【机器学习】神经网络简介以及如何用Tensorflow构建一个简单的神经网络

引言 神经网络是一种模拟人脑神经元连接和工作方式的计算模型&#xff0c;它是深度学习的基础&#xff0c;并在机器学习领域中扮演着重要角色 文章目录 引言一、神经网络简介1.1 结构组成1.2 工作原理1.3 学习过程1.4 应用领域1.5 感知器1.6 功能特点1.7 总结 二、用Tensorflow…

Python类的高阶用法

类的高阶用法在编程中&#xff0c;尤其是在面向对象编程&#xff08;OOP&#xff09;中&#xff0c;扮演着非常重要的角色。这些高阶用法不仅增强了类的功能和灵活性&#xff0c;还使得代码更加模块化和可重用。以下是一些类的高阶用法&#xff1a; 1. 类的继承与多态 继承&a…

linux服务 学习

服务&#xff08;Service&#xff09; 在Linux操作系统中&#xff0c;服务&#xff08;Service&#xff09;是一个基本概念&#xff0c;它通常指的是运行在后台的、持续提供特定功能或资源给系统内部组件或者网络上的客户端程序。 这些服务是系统正常运行和提供各种功能的关键…

NIOS Eclipse突然报错:No rule to make target `/system.h

NIOS Eclipse突然报错&#xff1a;No rule to make target /system.h 今天打开NIOS Eclipse编译昨天完好的工程&#xff0c;发现报错。我就纳闷了&#xff0c;代码没有修改&#xff0c;编译结果报错 控制台中problems选线中显示&#xff1a; No rule to make target /system…

MobileVit 系列算法

自 Vision Transformer 出现之后&#xff0c;人们发现 Transformer 也可以应用在计算机视觉领域&#xff0c;并且效果非常不错。但是基于 Transformer 的网络模型通常具有数十亿或数百亿个参数&#xff0c;这使得它们的模型文件非常大&#xff0c;不仅占用大量存储空间&#xf…

微信答题小程序产品研发-后端开发

在开发答题小程序的后端服务和数据库设计时&#xff0c;需要考虑API的设计、数据库模型的构建以及数据的安全性和一致性。 这里我采用了云开发&#xff0c;后端语言是Node&#xff0c;数据库是NoSql&#xff0c;然后我简单整理了各个功能模块的后端开发概要和数据库设计。 1. …

开放式耳机的优缺点?这里有开放式耳机推荐品牌

随着开放式耳机功能的增加和创新&#xff0c;导致很多人不知道开放式耳机哪款好&#xff0c;开放式耳机和封闭式耳机的优缺点有哪些&#xff1f;还有就是开放式耳机漏音严重吗&#xff1f;等问题。下面我来跟大家一起了解了解开放式耳机为什么好&#xff0c;有哪些值得入手的。…

【网络】UDP和TCP之间的差别和回显服务器

文章目录 UDP 和 TCP 之间的差别有连接/无连接可靠传输/不可靠传输面向字节流/面向数据报全双工/半双工 UDP/TCP API 的使用UDP APIDatagramSocket构造方法方法 DatagramPacket构造方法方法 回显服务器&#xff08;Echo Server&#xff09;1. 接收请求2. 根据请求计算响应3. 将…