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 决定:
UENUM(BlueprintType)
namespace EBTNodeResult
{
// keep in sync with DescribeNodeResult()
enum Type
{
Succeeded, // finished as success
Failed, // finished as failure
Aborted, // finished aborting = failure
InProgress, // not finished yet
};
}
在蓝图版本(继承 BTTask_BlueprintBase)的任务节点中,如果在执行任务第一时间就调用FinishExecute() 或 FinishAbort(),那就是瞬时任务(图6),否则就是持续时间的任务(图7)。
图6:打印完成后立刻结束(瞬间)
图7:持续5秒后结束(持续)
图8:蓝图版本的状态对应关系
在写C++的任务时,瞬间完成的任务这样写 ↓
// 瞬间完成的任务
EBTNodeResult::Type UBTTask_MyTask::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory)
{
// do something
return EBTNodeResult::Succeeded;
// return EBTNodeResult::Failed;
// return EBTNodeResult::Aborted;
}
持续一段时间的任务这样写 ↓
// 持续一段时间的任务
UBTTask_MyTask::UBTTask_MyTask()
{
bNotifyTick = true; // 执行TickTask()的必要设置
}
void UBTTask_MyTask::TickTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory, float DeltaSeconds)
{
// check 一段时间后结束
if(/*满足条件*/) FinishLatentTask(*BehaviorComp, EBTNodeResult::Succeeded);
}
既然有持续一段时间的任务存在,那必然有打断这种任务的情况发生。
这就需要完全理解装饰器的功能了。
(挖坑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)文章来源:https://www.toymoban.com/news/detail-754995.html
(挖坑服务)文章来源地址https://www.toymoban.com/news/detail-754995.html
其他:关于制作行为树的理解
- Pawn或者Character。角色可以指定一个AIController控制,比如一个狼狗角色,可以切换AIController,既可以是攻击你的怪物,也可以是NPC,或者是你可以用命令控制的宠物。角色内可以封装和角色相关的行为,角色中也有很多关键的状态,比如自定义一个bBettle战斗状态,这个状态如果在行为树中会用到,则可以将这个状态作为副本放在黑板值中,但是注意,不要在角色类中操作黑板值的更新。
- AIController。具备移动导航功能,感知相关功能,这个类用来驱动角色类行为,然后可以写修改黑板值的逻辑。比如,角色类中有个bBettle战斗状态,某个怪物感知到附近有攻击目标,AIController就会做2件事,设置角色的bBettle=True,设置黑板值BettleState==True。有时候你需要立刻停止正在执行的任务,可以调用UBehaviorTreeComponent::RestartTree方法。
- 行为树资源。由各种通用的节点组成的逻辑脚本集合,行为树资源在内存中是唯一的,也就是说,多个角色会公用一个行为树资源。
- 黑板资源和黑板值。用于传递数据。思考一下什么值需要变为黑板值存在呢?就是通用的用于判断子树或者任务能否执行的值,阻止做某件事,或者阻止做某件事的同时允许做某事。那就需要设置成黑板值了。黑板值可以简单的是角色属性的副本,更有可能是一个判断结果的临时保存。
- 任务。具体要做的事。可以去更新黑板值,比如更新一个位置信息。调用角色内封装好的行为。个人建议,不要在任务中写复杂的关于角色内部要做的事。另外不要在任务中绑定角色的代理,因为这个任务对于所有角色是共享的。
- 装饰器。条件判断,控制行为树子树的切换,控制是否执行节点。
- 服务。间隔更新黑板值。或者间隔判断某个情况,再更新黑板值。
- EQS:个人不建议在商业项目大规模使用。
到了这里,关于UE4行为树详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!