欢迎光临散文网 会员登陆 & 注册

TomLooman_ActionRoguelike_第十章基础AI与行为树

2023-08-11 17:42 作者:别叫我小红  | 我要投稿

该专栏用于保存对TomLooman的ActionRoguelike项目的学习笔记,学习过程中的思考与记录不一定准确。


教程参考:https://github.com/tomlooman/ActionRoguelike

基于UE5.0的项目实现:https://github.com/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial

2023_08_08

基础AI与行为树:机器人的行为和运动,自定义检查攻击范围的行为树服务

 

AI机器人类似于玩家控制的角色,不同的是它受到AIController的控制,因此要创建AI机器人,我们创建一个Character类和一个AIController类。

SAICharacter与之前创建的SCharacter类似,但不需要实现SetupPlayerInputComponent函数,因为AI机器人不需要玩家的输入。

不同的是,在SAICharacter的蓝图子类中,我们要将自己创建的AIController蓝图子类赋值给AI Controller Class。

在AIController中,我们可以通过RunBehaviorTree执行行为树来控制机器人的行为,为此我们需要先在AIController类中声明一个行为树变量,然后在编辑器中赋值。

 

我们可以如下创建自己的行为树和黑板,其中行为树控制对象行为逻辑,黑板则保存行为树中要用到的数据。

在行为树中,我们可以将黑板赋给行为树,这样就能在行为树中使用黑板存储的数据。

我们先制作如下的简单行为树,其中Sequence节点从左至右顺序执行其子节点,但执行完所有子节点或者某个子节点失败时停止。MoveTo节点使用导航系统移动到某个位置或Actor,由黑板中的变量决定。

我们创建了一个行为树后,还要将其赋值给AIController的行为树成员变量,这样才能通过AIController控制机器人执行该行为树。

所以,AICharacter、AIController、行为树、黑板之间的关系是,AICharacter的行为受AIController控制,AIController可以拥有行为树成员变量,并让AICharacter执行行为树,行为树拥有黑板,可以使用黑板中的数据。

另外,要让机器人运动,我们要给机器人划定导航网格(可能是因为类似MoveTo的行为用到了导航网格),也就是下图中的绿色区域,我们在PlaceActor中可以放置导航网格,并在世界中对导航网格进行放大和缩小。

 

上面的行为树暂时还无法执行,因为我们在MoveTo节点用到了黑板中的Vector变量MoveToLoaction,当现在还没人给这个变量赋值。那么我们就应该在某个地方给这个变量赋值,我们在AIController中执行这一步操作(可能是因为行为树是AIController的成员变量,被它直接调用)。

不要被下面的代码误导,我们并不是通过机器人的Pawn来访问行为树的黑板,这里的MyPawn只是为了获得玩家的位置,然后将位置值赋给黑板中的变量。我们也不需要先访问行为树成员变量,再访问行为树的黑板,而是可以通过GetBlackboardComponent直接访问黑板,并用SetValueAsXXX给黑板中某个类型的变量赋值。

除了向上面那样用MoveTo节点让机器人移动到某个具体位置之外,我们还可以让机器人移动到某个Actor,实现方式类似。当然,此时黑板中要存在一个Actor类型的变量,

 

当我们测试机器人时,有两种方法,可以结合使用。

第一种是直接看行为树的执行流,以及黑板中的变量,跟测试蓝图类似,

第二种是运行时按引号键,使用GameplayDebugger,

为了让信息在Gameplay调试器中更加易读,我们可以在行为树中修改节点的名字,

当世界中有两个机器人时,按下引号键时会显示处于屏幕中心的机器人的信息。

 

因为我们希望机器人是远程单位,能够在距离角色一定距离的地方停下,所以我们对行为树进行如下修改,

其中的Selector节点不同于Sequence节点,它不会顺序执行所有子节点,而是只执行第一个为true的子节点,所以配合Blackboard节点使用。Blackboard节点会检查给定的黑板键,也就是黑板中的变量,是否设置了值,类似于检查空值的if语句。

这里Blackboard节点检查黑板中一个名为bWithinAttackRange的bool变量是否为false,这变量表示玩家是否在机器人的射程内。那么从逻辑上来说,在机器人运行过程中,应该会有一个函数,在每个Tick计算机器人与玩家的距离,并与机器人的射程比较。

要实现这样的Tick效果,我们可以使用行为树中的Service(服务)节点。Service节点的特点是,当其分支被执行,节点就会以一定频率执行,这些节点常用于检查和更新黑板。

我们可以通过Add Service添加Service节点,Service节点具体的执行内容由我们自己定义。我们可以创建行为树Service节点类,并实现其中的TickNode成员函数。

 

TickNode的实现如下。

我们从想求的目标出发思考实现中需要得到哪些东西。我们最终需要得到一个bool值,这个bool值表示玩家是否在机器人的攻击范围内。为了得到这个bool值,我们需要求得机器人与玩家之间的距离,那么就需要得到指向玩家和机器人的指针。

(在以下逻辑中要注意,我们的Service节点是附着在行为树上的,所以能直接访问到的是行为树。而且和使用行为树的AIController不同,Service节点不能直接访问黑板。)

然后再来看这个函数的实现。先考虑怎么获得玩家的指针,回想我们这个行为树的逻辑,机器人在离玩家一定距离时停下,所以有一个MoveTo(TargetActor)的节点,那么黑板中也就必然有一个TargetActor对象(在AIController的构造函数中赋值),那么我们就可以通过访问黑板来得到指向玩家的指针。因此,我们先通过OwnerComp.GetBlackboardComponent

得到黑板组件,OwnerComp是行为树的引用。再通过BlackboardComp->GetValueAsObject("TargetActor")得到黑板中的TargetActor变量,也就是指向玩家的指针,因为GetValueAsObject,返回的是UObject类型的指针,所以还需要进行Cast。

然后我们要考虑怎么获取指向机器人的指针,我们现在能访问到行为树组件,而行为树组件又被AIController拥有,所以我们能访问AIController,而AIController又直接控制着机器人,这样我们就能访问到机器人了。因此,我们先通过OwnerComp.GetAIOwner得到AIController,再通过AIController->GetPawn得到AIController控制的机器人。

得到玩家和机器人后,通过FVector::Distance就能得到两者在世界中的距离,并与表示机器人攻击距离的Service节点类的成员变量作比较,得到想求的bool值。

我们又在上面的基础之上多做了一步。因为有时即使玩家和机器人距离很近,但是要是隔着一堵墙,机器人实际上也无法攻击到玩家,所以最后返回的bool值应该是上面距离的bool与“机器人能否看到玩家”的与逻辑(这里先不考虑空气墙的情况)。通过AIController->LineOfSightTo(TargetActor)我们可以得到“机器人能否看到玩家”的结果。

最终,我们通过BlackboardComp->SetValueAsBool(AttackRangeKey.SelectedKeyName, bWithinRange && bCanSee);对黑板中的变量进行赋值。其中,AttackRangeKey.SelectedKeyName可以用硬编码,也就是距离的变量名替代,也可以创建FName类型的成员变量。推荐的是像这里一样在Service节点类中创建一个黑板条目类型的成员变量,然后在行为树的具体Service节点中与黑板中的变量进行绑定。

 

在上述代码的编译中会遇到一个unresolved external symbol报错,具体原因没听明白。解决这个报错,要在Build.cs中进行修改。具体看教程。

 

我们希望当玩家远离机器人时,机器人会立刻接近玩家,而不是执行Wait节点5s,再去接近玩家。也就是说,当判断距离的bool变量改变时,应该中断当前节点的任务,重新执行父节点。

之前我们提到Blackboard节点类似于一个if函数,它能判断黑板的某个变量是否为空。Blackboard节点中的ObserverAborts参数还可以用来控制中断,在ObserverAborts不是None时,Blackboard节点将监听行为树中的其它任务。当Blackboard节点判断为true时,触发ObserverAborts参数规定的中断类型,中断行为树中的任务,并执行更高优先级的任务。

中断类型包括Self,LowerPriority和Both。当值为Self时,Blackboard节点会监听自己这棵子树,当中断条件被满足时,中断自身。当值为LowerPriority时,Blackboard节点监听比自己优先级低的子树,当中断条件被满足时,会中断这个优先级更低的节点。Both则会同时监听并中断自身和优先级更低的节点。

节点的优先级同时由节点类型、条件判断和ObserverAborts参数的设置决定。深度越深的节点,优先级越低。这里因为条件判断和ObserverAborts参数的原因,左节点比右节点的优先级高。


TomLooman_ActionRoguelike_第十章基础AI与行为树的评论 (共 条)

分享到微博请遵守国家法律