unity行为树简介

摘要:
目前在Unity3D游戏中一般复杂的AI都可以看到行为树的身影,简单的AI使用状态机来实现就可以了,所以这里我也是简单的学习下,为以后用到做准备。一般条件节点出现在Sequence控制节点中,其后紧跟条件成立后的Action节点。取而代之的做法是使用BehaviorMasks,EncounterAttitude,Inhibitions。

目前在Unity3D游戏中一般复杂的AI都可以看到行为树的身影,简单的AI使用状态机来实现就可以了,所以这里我也是简单的学习下,为以后用到做准备。

行为树的概念出现已经很多年了,总的来说,就是使用各种经典的控制节点+行为节点进行组合,从而实现复杂的AI。

Behavior Designer插件里,主要有四种概念节点,都称之为Task。包括:

(1)Composites 组合节点,包括经典的:Sequence,Selector,Parallel

(2)Decorator装饰节点,顾名思义,就是为仅有的一个子节点额外添加一些功能,比如让子task一直运行直到其返回某个运行状态值,或者将task的返回值取反等等

(3)Actions 行为节点,行为节点是真正做事的节点,其为叶节点。Behavior Designer插件中自带了不少Action节点,如果不够用,也可以编写自己的Action。一般来说都要编写自己的Action,除非用户是一个不懂脚本的美术或者策划,只想简单地控制一些物件的属性。

(4)Conditinals条件节点 ,用于判断某条件是否成立。目前看来,是Behavior Designer为了贯彻职责单一的原则,将判断专门作为一个节点独立处理,比如判断某目标是否在视野内,其实在攻击的Action里面也可以写,但是这样Action就不单一了,不利于视野判断处理的复用。一般条件节点出现在Sequence控制节点中,其后紧跟条件成立后的Action节点。

行为树(Behavior Tree)具有如下的特性:
它的4大类型的节点:1. Composite 2.Decorator 3.Condition 4.Action Node
任何Node被执行后,必须向其Parent Node报告执行结果:成功 / 失败。
这简单的成功 / 失败汇报原则被很巧妙地用于控制整棵树的决策方向。

一: Composite Node(组合节点)

Composite节点按照复合性质还可以细分为3种:

* Selector Node

当执行本类型Node时,它将从begin到end迭代执行自己的Child Node:
如遇到一个Child Node执行后返回True,那停止迭代,
本Node向自己的Parent Node也返回True;否则所有Child Node都返回False,
那本Node向自己的Parent Node返回False。

* Sequence Node

当执行本类型Node时,它将从begin到end迭代执行自己的Child Node:
如遇到一个Child Node执行后返回False,那停止迭代,
本Node向自己的Parent Node也返回False;否则所有Child Node都返回True,
那本Node向自己的Parent Node返回True。

* Parallel Node

并发执行它的所有Child Node。
而向Parent Node返回的值和Parallel Node所采取的具体策略相关:
Parallel Selector Node: 一False则返回False,全True才返回True。
Parallel Sequence Node: 一True则返回True,全False才返回False。
Parallel Hybird Node: 指定数量的Child Node返回True或False后才决定结果。

Parallel Node提供了并发,提高性能。
不需要像Selector/Sequence那样预判哪个Child Node应摆前,哪个应摆后,
常见情况是:
(1)用于并行多棵Action子树。
(2)在Parallel Node下挂一棵子树,并挂上多个Condition Node,
以提供实时性和性能。
Parallel Node增加性能和方便性的同时,也增加实现和维护复杂度。

PS:上面的Selector/Sequence准确来说是Liner Selector/Liner Sequence。
AI术语中称为strictly-order:按既定先后顺序迭代。
Selector和Sequence可以进一步提供非线性迭代的加权随机变种。
Weight Random Selector提供每次执行不同的First True Child Node的可能。
Weight Random Sequence则提供每次不同的迭代顺序。
AI术语中称为partial-order,能使AI避免总出现可预期的结果。

二:Decorator Node(装饰节点)

装饰节点的功能正如它的字面意思:它将它的Child Node(孩子节点)执行后返回的结果值做额外处理(装饰)后,再返回给它的Parent Node(父节点)。很有些AOP的味道。
比如Decorator Not/Decorator FailUtil/Decorator Counter/Decorator Time…
更geek的有Decorator Log/Decorator Ani/Decorator Nothing…

三:Condition Node(条件节点)

条件节点很直白,它仅当满足Condition节点的条件时返回True。

四:Action Node(行为节点)

行为节点是完成具体的一次(或一个step)的行为,视需求返回值。
而当行为需要分step/Node间进行时,可引入Blackboard进行简单数据交互。

注意:

整棵行为树中,只有Condition Node和Action Node才能成为Leaf Node(叶子节点),而也只有Leaf Node才是需要特别定制的Node;Composite Node和Decorator Node均用于控制行为树中的决策走向。(所以有些资料中也统称Condition Node和ActionNode为Behavior Node,而Composite Node和Decorator Node为Decider Node。)
更强大的是可以加入Stimulus和Impulse,通过Precondition来判断masks开关。
通过上述的各种Nodes几乎可以实现所有的决策控制:if, while, and, or, not, counter, time, random, weight random, util…

总的来说

行为树具有如下几种优点,确实是实现AI框架的利器,甚至是一种通用的可维护的复杂流程管理利器:

> 静态性

越复杂的功能越需要简单的基础,否则最后连自己都玩不过来。
静态是使用行为树需要非常着重的一个要点:即使系统需要某些"动态"性。
其实诸如Stimulus这类动态安插的Node看似强大,但却破坏了本来易于理解的静态性,弊大于利。
Halo3相对于Halo2对BT AI的一个改进就是去除Stimulus的动态性。取而代之的做法是使用Behavior Masks,Encounter Attitude,Inhibitions。
原则就是保持全部Node静态,只是根据事件和环境来检查是否启用Node。
静态性直接带来的好处就是整棵树的规划无需再运行时动态调整,为很多优化和预编辑都带来方便。

> 直观性

行为树可以方便地把复杂的AI知识条目组织得非常直观。默认的Composite Node的从begin往end的Child Node迭代方式就像是处理一个
预设优先策略队列,也非常符合人类的正常思考模式:先最优再次优。
行为树编辑器对优秀的程序员来说也是唾手可得。

> 复用性

各种Node,包括Leaf Node,可复用性都极高。实现NPC AI的个性区别甚至可以通过在一棵共用的行为树上不同的位置来安插Impulse来达到目的。当然,当NPC需要一个完全不同的大脑,比如70级大BOSS,与其绞尽脑汁在一棵公用BT安插Impulse,不如重头设计一棵专属BT。

> 扩展性

虽然上述Node之间的组合和搭配使用几乎覆盖所有AI需求。
但也可以容易地为项目量身定做新的Composite Node或Decorator Node。
还可以积累一个项目相关的Node Lib,长远来说非常有价值。

Conditional Abort 机制

Conditional Abort允许你的行为树动态的响应变化,而不是被各种中断/执行的任务搞的你的树乱七八糟。这一特性跟虚幻4中的Abort很相似,很多行为树,都会在每帧重新评估整颗树。ConditionAbort是一个优化算法,让你不必每次返回整颗树。

如下图:
unity行为树简介第1张

非Abort模式:当这棵树运行的时候,Conditional会返回Success,然后Sequence会执行下一个子节点,Wait。Wait节点会等待10秒钟。

当Wait节点正在运行的时候,假定Condition发生了变化返回了Failure,Wait也不会停止,会一直等待10秒结束。
Abort模式:如果Sequence的Abort被激活,那么Conditional节点会发起一个abort,当Wait处于Running状态时,如果Conditional状态发生变化,会中断wait的执行。Condtional Abort可以在任何的组合节点获取。
unity行为树简介第2张
我的解读:
Conditional Abort,只能由条件节点发起,这是因为只有条件节点会判断当前条件,并且在条件变化时,发起中断信息,终止一个正在Runing的行为节点。
【1】存在 Conditonal 节点
【2】存在Action节点,并且这个节点不是立刻返回,而是需要返回 Runing的。
【3】Action的节点的运行,依赖于Conditional节点的返回值。
【4】当条件变化时,Conditional节点返回一个abort请求
【5】那么谁来捕获这个Conditional变化呢?可以Conditonal的父节点,或者是Conditional父节点的兄弟节点。(装饰节点)
【6】当捕获到Abort之后干什么呢?中断当前running的节点,从新评估整颗树。
【7】好处是什么呢?动态改变行为树的执行顺序,不必非等到Wait节点10秒钟执行完毕,才重新检索整棵树,才检测Conditional的返回值。

然后我们来看看有几种中断类型:

unity行为树简介第3张

None:这是默认的行为,不会中断,一旦Wait开始运行,他就必须傻了吧唧的运行完10秒钟,哪怕期间条件变化了。

比如,你发现周围没人,决定休息10秒,然后敌人来了,你还在休息,敌人把毫无反抗的你给啪啪啪了。
unity行为树简介第4张
Self:这是自身条件中断,Conditional Abort只能中断同级(拥有相同的父节点(装饰节点)的Action节点。
比如,你刚决定休息,休息了不到1秒,敌人来了。你决定中断休息这个行为,开始做点什么,而不是坐以待毙。
unity行为树简介第5张
Lower Priority 代表中断低优先级节点,
行为树,越靠左边,优先级越高,越靠右边,优先级越低。
Lower Priority意义在于当一个高优先级装饰节点A下的Condiontal子节点发生变化时,他只能中断一个低优先级的Action节点(跟A节点同级,但在A节点右侧的Action节点)。
假设一个AI包含有 回避 攻击 休息三个模块(大分支)
1-你正在攻击,这时候有人朝你开枪了,回避分支发现了这个问题,要求你先保命,于是中断了你的攻击分支。
2-而如果你正在回避,这时候回避分支自身发生了变化——攻击你的人死了——他不会中断你当前的回避行为。
unity行为树简介第6张

Both综合Self和Lower Priority的中断

1-你正在攻击,这时候有人朝你开枪了,回避模块发现了这个问题,要求你先保命,于是中断了你的攻击行为。
2-而如果你正在回避,这时候回避模块发生了变化——攻击你的人死了——那你还回避干屁,于是回避行为也终止了。

最后在总结一下干货:
Conditional Abort由 Conditonal节点发起,由Composite节点截获,并决定是否要abort。
如果决定Abort,会从发起Abort的节点开始,重新评估。
Self 是指:同根的节点
Lower 则是指:和发起Abort的父节点(Composite)相对而言,低优先级的节点。
unity行为树简介第7张
如上图,Sequence节点选择了Lower Priority ,那么当子节点Action在Running状态时,子节点的Conditional是中断不了的,但是当右侧的Sequence下的Action在Running状态时,如果左侧的Conditional条件发生了变化,是可以另树的逻辑重新执行,执行自己的Action而中断右侧Sequence(低优先级装饰节点)下的Action。
unity行为树简介第8张
并且此时左侧Sequence下的Conditional拥有一个带有循环图案的对号。


Conditional Abort条件是可以嵌套的,如下图:
unity行为树简介第9张

当能看见,和能听见,任何一个节点返回True的时候,将执行Action。
一个恰当的例子,比如,一个守卫,当他看见敌人,或者听到异常声音的时候,他会开始巡逻一圈。

Selector被设置成LowerPriority,这样,当“听”和“看”条件变化时,可以中断巡逻Action。
当敌人不见踪影,也没了声音,守卫开始怀疑自己是不是幻听幻视了,中断巡逻。

Sequence被设置成LowerPriority,是希望Selector被重新评估时,他可以中断比Sequence优先级低的其他节点上正在运行的行为。
比如,现在CanSee和CanHear都是返回False的,因此巡逻Action是不执行的。
假设Sequence的兄弟节点,有个抽烟的动作正在Runing。这时候风吹草动了,即CanSee和CanHear条件发生变化时,抽烟Action会中断,而巡逻Action会被执行。

但是如果Sequence是Self而不是LowerPriority的话。即使风吹草动了,他也不会终止抽烟而开始巡逻的。
这里LowerPriority改成Both也是可以的,虽然这并没有多大意义。

而Selector,如果他没有被设置为Both或者LowerPriority,那么这两个条件发生变化,这个Sequence根本不会被重新执行,也无法执行中断巡逻Action,那么肯定也无法中断Sequence的低优先级节点。

免责声明:文章转载自《unity行为树简介》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法一个神奇SQL引发的故障【转】下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

LINUX进程组调度机制分析【转】

转自:https://oenhan.com/task-group-sched 又碰到一个神奇的进程调度问题,在系统重启过程中,发现系统挂住了,过了30s后才重新复位,真正系统复位的原因是硬件看门狗重启的系统,而非原来正常的reboot流程。硬件狗记录的复位时间,将不喂狗的时间向前推30s分析串口记录日志,当时的日志就打印了一句话:“sched: RT th...

Nodejs介绍、安装及简单使用

打开Nodejs英文网:https://nodejs.org/en/ 中文网:http://nodejs.cn/ 我们会发现这样一句话: 翻译成中文如下: Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 Node.js 的包管理器...

如何用nodejs 开发一个命令行交互工具

参考地址1 参考地址2 一、npm package.json bin 1、package.json { "name": "test", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Erro...

C#综合揭秘——细说多线程(上)

引言   本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发。   其中委托的BeginInvoke方法以及回调函数最为常用。   而 I/O线程可能容易遭到大家的忽略,其实在开发多线程系统,更应该多留意I/O线程的操作。特别是在ASP.NET开发当中,可能更多人只会留意在客户端使用Ajax...

Linux内核:关于中断你需要知道的

1、中断处理程序与其他内核函数真正的区别在于,中断处理程序是被内核调用来相应中断的,而它们运行于中断上下文(原子上下文)中,在该上下文中执行的代码不可阻塞。中断就是由硬件打断操作系统。 2、异常与中断不同,它在产生时必须考虑与处理器时钟同步。异常被称为同步中断,例如:除0、缺页异常、陷入内核(trap)引起系统调用处理程序异常。 3、不同的设备对应的中断不...

react源码解析7.Fiber架构

react源码解析7.Fiber架构 视频课程(高效学习):进入课程 课程目录: 1.开篇介绍和面试题 2.react的设计理念 3.react源码架构 4.源码目录结构和调试 5.jsx&核心api 6.legacy和concurrent模式入口函数 7.Fiber架构 8.render阶段 9.diff算法 10.commit阶段 11.生命...