介绍行为树
不像有限状态机,一棵行为树是一个分层节点的树,节点控制着决策流,任务的执行或者我们进一步称之为“行为”。
输的叶子节点是实际的命令,例如,协调组件与系统的其他部分相交互的地方。
例如,在一个以服务导向的结构中,叶子将会包含客户端节点,这些节点与执行操作的服务器相通信。
在以下的例子中,可以看到在一个顺序中有两个行为被执行,DetectObject以及GraspObject
树的其他节点,非叶子节点,控制着执行流。
为了更好理解控制流的发生,想象一个信号,这里称之为"tick",这个tick在树的根节点开始被执行,然后它通过分支进行传播直到它到达一个或者多个叶子。
这里的tick意味着去唤醒一个树节点的回调函数tick()。
当一个TreeNode被ticked之后,它会返回一个节点状态NodeStatus,可能是以下之一:
SUCCESS
FAILURE
RUNNING
头两个,自名其义,告知其父节点他们的操作是成功或者失败。
RUNNING 通过一个异步节点来返回,该节点的执行还没有完成或者需要更多的时间来返回一个有效的结果的时候。
异步节点可能被停止。
一个节点的返回结果向后传播给其父节点,这个结果解决了哪一个子节点会被ticked或者可能将返回一个结果给到它的父节点。
节点类型:
控制节点ControlNodes可能有一个到N个子节点,一旦一个tick被接收到,这个tick可能被传播到一个或者更多的子节点。
装饰节点DecoratorNodes其类似于控制节点,但其只有一个子节点。
行为节点ActionNodes是叶子节点,用户应该实现ActionNodes来执行实际的任务。
条件节点ConditionNodes类似于行为节点,但是他们总是原子性的,同步的;例如他们不会返回RUNNING结果,他们不会改变系统的状态。
例子
为了更好理解行为树是怎样工作的,关注一些实际的例子。为了简化的目的,不会考虑当一个行为返回RUNNING的时候会发生什么。
假定每一个行为是自动的且同步的执行。
第一个控制节点:Sequence
一个控制节点的子节点总是有序的;在图形表示中,执行序列是从左到右。
简言之:
如果一个孩子节点返回SUCCESS,那么tick下一个。
如果一个孩子节点返回FAILURE,然后不再有孩子节点被tick,那么这个Sequence返回FAILURE。
如果所有的孩子返回SUCCESS,那么序列也会返回SUCCESS。
对于上面的图形是有bug的,例如,如果GrabBeer失败,那么冰箱会一直开着,因为最后的行为关闭冰箱没有被执行。
装饰节点Decorators
取决于装饰节点的类型,这个节点的目标可能是以下之一:
转换其孩子节点的返回结果;
停止孩子节点的执行;
重复tick孩子节点,取决于装饰节点的类型。
可以扩展你的语法来创建自己的装饰节点
节点inverter是一个装饰节点,它目的是对其子节点返回的结果取反;
节点Retry会重复tick其子节点3次如果子节点返回FAILURE的话。
如果DoorOpen返回失败,那么再逆转一下,返回成功,然后继续tickRetry节点,跟期望的行为一致,如果DoorOpen返回成功,经过取反之后,返回失败,由于Sequence的特点,不会之后Retry,那么Sequence也会返回之后,再进入到根节点也会返回失败,从而不会执行EnterRoom,那么整个Sequence就被中断了。
第二个控制节点:Fallback
FallbackNodes称之为“Selectors”,正如其名字表达的意思,可以表达回退的策略,例如如果子节点返回失败,下一步该做什么。
按序tick孩子节点:
如果孩子节点返回FAILURE,tick下一个。
如果一个孩子节点返回成功,后面不会有孩子节点被tick,然后Fallback返回成功。
如果所有的孩子节点返回失败,那么Fallback才会返回失败。
下面例子中,关于Sequences和Fallback一起使用
回顾"Fetch me a beer"
下面绿色表示执行成功,红色表示失败,黑色表示还没执行
上面二者均可关掉冰箱,
左边总是会返回成功,不管拿没拿到beer;
右边当有啤酒的时候返回成功,否则返回失败;