当前位置:   article > 正文

UE4行为树详解_ue4 行为树任务hurttrapfirst

ue4 行为树任务hurttrapfirst

UE4行为树详解(持续更新,才怪)

ksun

139 人赞同了该文章

前言

本文的目的是希望帮助开发者能更好地理解行为树执行顺序,并更合理的实现AI逻辑。而且尽量说人话

需要一定的基础,希望你至少做了几个简单AI。

很久没有碰AI开发了,我鸽了,对不起,此文请配合评论一起食用(我对AI开发的部分概念,理解有错误)

关于行为树的博客文章

这些都写的很棒哎。

[UE4][AI] 浅析UE4-BehaviorTree的特性102 赞同 · 10 评论文章​编辑

0.UML类图

下列类图描述了,UE4引擎中,Runtime/AIModule/Classes/BehaviorTree文件夹内,重要的类之间的关系。

图1:行为树核心类图

图2:UBTTaskNode类图

图3:UBTDecorator类图

图4:UBTService类图

图5:黑板值类型的类图


1.Composites 节点

这一小节,如果没完全看懂,可以先往后看。

Sequence节点

Sequence节点。顺序节点,依次执行下级节点,若下级的所有节点都返回 Succeeded,则Sequence节点本身返回 Succeeded;若任何一个下级节点返回 Failed,则停止执行后续的下级节点,并且Sequence节点本身返回 Failed;如果 Sequence 节点下方没有任务节点,返回 Failed。

Selector节点

Selector节点。选择节点,从左到右依次选择执行下级节点,若有任何一个节点返回 Succeeded,则停止执行后续下级节点,并返回 Succeeded;若全部的下级节点都返回 Failed,则此 Selector返回 Failed;如果 Selector 节点下方没有任务节点,返回 Failed。

ApplyDecoratorScope选项

Sequence 和 Selector 都有 ApplyDecoratorScope 选项,意思是开启装饰器的作用域,当勾选 ApplyDecoratorScope 则这个Composites下级节点的装饰器,若执行状态不在所处的作用域,则装饰器是无效的。

为了更直观理解这个选项,你可以自己动手做一个例子(图15)。在执行“第二个Wait”时,若改变黑板值,令黑板值等于1,因为 ApplyDecoratorScope 等于 True,无法切换到“第一个Wait”。

SimpleParallel节点。并行节点,左边紫色的部分,必须连接一个任务节点,可称这个任务为“主要任务”,右边灰色的部分可以连 Composites 节点或者任务节点,是与主要任务并行执行(同时执行)逻辑所在的位置。这也意味着你可以并行节点的并行部分再放并行节点

当主要任务返回 Succeeded,此SimpleParallel 节点返回 Succeeded;若主要任务返回 Failed,此 SimpleParallel 返回 Failed;SimpleParallel 的返回结果和并行部分的结果没有关系。

并行节点有一个 FinishMode 选项。若是 Immediate 时,则主要任务停止,并行任务立刻停止。若是 Delayed 时,则会等待并行任务执行结束。

多试验。

2.任务节点

任务节点的作用是做特定的事情,是描述谁在什么时间做了什么,在行为树的最底部(叶子节点),是AI行为的终点。比如,移动到你的左边10cm,转身面向猎空,发出2次“哇”的声音。

你可以做任何事。

每个任务都有执行时间,目标,任务结束时的状态。我们可以把任务节点的执行时间简单的分为2种:

  • 瞬间执行完成的任务
  • 持续一段时间的任务(非瞬间完成)

任务节点的状态由 EBTNodeResult 决定:

  1. UENUM(BlueprintType)
  2. namespace EBTNodeResult
  3. {
  4. // keep in sync with DescribeNodeResult()
  5. enum Type
  6. {
  7. Succeeded, // finished as success
  8. Failed, // finished as failure
  9. Aborted, // finished aborting = failure
  10. InProgress, // not finished yet
  11. };
  12. }

在蓝图版本(继承 BTTask_BlueprintBase)的任务节点中,如果在执行任务第一时间就调用FinishExecute() 或 FinishAbort(),那就是瞬时任务(图6),否则就是持续时间的任务(图7)。

图6:打印完成后立刻结束(瞬间)

图7:持续5秒后结束(持续)

图8:蓝图版本的状态对应关系

在写C++的任务时,瞬间完成的任务这样写 ↓

  1. // 瞬间完成的任务
  2. EBTNodeResult::Type UBTTask_MyTask::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
  3. uint8* NodeMemory)
  4. {
  5. // do something
  6. return EBTNodeResult::Succeeded;
  7. // return EBTNodeResult::Failed;
  8. // return EBTNodeResult::Aborted;
  9. }

持续一段时间的任务这样写 ↓

  1. // 持续一段时间的任务
  2. UBTTask_MyTask::UBTTask_MyTask()
  3. {
  4. bNotifyTick = true; // 执行TickTask()的必要设置
  5. }
  6. void UBTTask_MyTask::TickTask(UBehaviorTreeComponent& OwnerComp,
  7. uint8* NodeMemory, float DeltaSeconds)
  8. {
  9. // check 一段时间后结束
  10. if(/*满足条件*/) FinishLatentTask(*BehaviorComp, EBTNodeResult::Succeeded);
  11. }

既然有持续一段时间的任务存在,那必然有打断这种任务的情况发生。

这就需要完全理解装饰器的功能了。

(挖坑IgnoreRestartSelf)

3.装饰器节点

装饰器是作用在行为树中的一种节点,作用是控制其他节点是否执行,或者打断正在执行的任务。

图9:BlackBoard装饰器

作为例子,引擎自带的BlackBoard装饰器(图9)的功能是,各种黑板值与填入的常量值做比较,根据比较结果控制修饰节点是否执行。

tips:ROOT下方的第一个Selector节点上方,装饰器的颜色不一样,因为这个装饰器是和RunBehavior这个任务节点,RunDynamicBehavior这个任务节点有关。我没有尝试过把复杂的行为树简化为多个子树,一般我不会在ROOT下方第一个Composites节点使用装饰器。

如何理解装饰器的ObserverAborts?

图10:装饰器的ObserverAborts,4个可选项

当装饰器上级的 Composites 节点是 Selector(图10),则 ObserverAborts 有4个可选值:

  • None
  • Self
  • LowerPriority
  • Both

图11:装饰器的ObserverAborts,2个可选项

当装饰器上级的 Composites 节点是 Sequence(图11),则 ObserverAborts 有2个可选值:

  • None
  • Self

解释4个选项的区别:

  • None:选了None的装饰器不起任何作用。个人认为是为了方便调试,你可以不用删除这个装饰器,选择None,可以忽略这个装饰器判断。
  • Self:(可以立刻停止自身的)若装饰器的计算结果为True,则执行修饰的任务,如果装饰器修饰Composites节点,则允许继续查找下级节点。如果装饰器结果是false,则不执行修饰的任务,如果这个任务修饰正在持续执行的任务,当装饰器计算结果由True改变为False时,当前任务会停止;如果装饰器修饰Composites节点,则不会继续查找下级节点。
  • LowerPriority:(可以立刻停止低优先级的)若当前装饰器计算结果为False,则不能执行当前修饰的节点,要执行比这个优先级低的节点。若装饰器计算结果True,会执行当前节点,并且会停止比当前节点低优先级的各种节点。进一步理解,当有一个任务是持续任务正在执行,修饰这个任务的 LowerPriority 装饰器,从True变成False,则不会停止持续任务,因为不是Self,但是如果这个任务结束了,下次再想执行,就不能执行了,因为装饰器是False。
  • Both:(既可以立刻停止自身的,又可以立刻停止低优先级的)单词意思是两者,哪两者?Self和Lower Priority。当这种装饰器为True,则可以执行当前节点,停止低优先级的节点。如果这装饰器为False,则停止当前修饰的节点,执行低优先级的节点。

看了上面糊涂没有呢,前两个还能明白,后面的优先级是什么意思?先来看看下面的例子(图12)。

图12:子树优先级,数字越小,优先级越高

每个节点的右上角有个小数字,数字越小优先级越高。

tips:一定要注意,放行为树的节点要整整齐齐,否则会出大问题,比如(图13),白色线段是按ABCD顺序执行,但实际上角标数字提示我们任务是按BACD顺序执行的。

图13

(挖坑NotifyObserver)


(挖坑服务)


其他:关于制作行为树的理解

  • Pawn或者Character。角色可以指定一个AIController控制,比如一个狼狗角色,可以切换AIController,既可以是攻击你的怪物,也可以是NPC,或者是你可以用命令控制的宠物。角色内可以封装和角色相关的行为,角色中也有很多关键的状态,比如自定义一个bBettle战斗状态,这个状态如果在行为树中会用到,则可以将这个状态作为副本放在黑板值中,但是注意,不要在角色类中操作黑板值的更新。
  • AIController。具备移动导航功能,感知相关功能,这个类用来驱动角色类行为,然后可以写修改黑板值的逻辑。比如,角色类中有个bBettle战斗状态,某个怪物感知到附近有攻击目标,AIController就会做2件事,设置角色的bBettle=True,设置黑板值BettleState==True。有时候你需要立刻停止正在执行的任务,可以调用UBehaviorTreeComponent::RestartTree方法。
  • 行为树资源。由各种通用的节点组成的逻辑脚本集合,行为树资源在内存中是唯一的,也就是说,多个角色会公用一个行为树资源。
  • 黑板资源和黑板值。用于传递数据。思考一下什么值需要变为黑板值存在呢?就是通用的用于判断子树或者任务能否执行的值,阻止做某件事,或者阻止做某件事的同时允许做某事。那就需要设置成黑板值了。黑板值可以简单的是角色属性的副本,更有可能是一个判断结果的临时保存。
  • 任务。具体要做的事。可以去更新黑板值,比如更新一个位置信息。调用角色内封装好的行为。个人建议,不要在任务中写复杂的关于角色内部要做的事。另外不要在任务中绑定角色的代理,因为这个任务对于所有角色是共享的。
  • 装饰器。条件判断,控制行为树子树的切换,控制是否执行节点。
  • 服务。间隔更新黑板值。或者间隔判断某个情况,再更新黑板值。
  • EQS:个人不建议在商业项目大规模使用。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/216277
推荐阅读
相关标签
  

闽ICP备14008679号