第一部分见这里
模式3:根据条件跳转到多个状态,包括自跳转
这是在状态机里最常见的模式,由于是基于条件的跳转,所以可以非常方便的用选择节点和前提的组合来描述,特别值得注意的是,对于自跳转而言,其实就是维持了当前的状态,所以,在构建行为树的时候,我们不需要特别考虑自跳转的转换。如下图所描述了,我们先用序列节点来保证跳转的上下文(可以参考模式2中的相关内容),这里用到的另一个技巧是,我们会在状态a结束的时候,在黑板中记录其结束的原因,以供后续的选择节点来选择。另外,我们在第二层选择节点第一次用到了非优先级的选择节点,和带优先级的选择节点不同,它每次都会从上一次所能运行的节点来运行,而不是每次都从头开始选择。
当然,和模式2类似的是,我们也可以不用序列节点,而是单纯的用选择节点,这样的话,作为默认状态的状态a就需要处在选择节点的最后一个,因为仅当所有跳转条件都不满足的时候,我们才会维持在当前的状态。如上图的下面那颗行为树那样。请仔细查看,我在前三个节点对于前提的定义,除了本身的跳转条件外,还加上了一个额外的条件,InAXState,它保证了仅在上一次运行的是A状态或自身的时候,我们才会运行当前的节点,这样就保证了和原本状态机描述是一致的。
模式4:循环跳转
在状态机中存在这样一种模式,在状态a中,根据某条件1,会跳转到状态b中,而在状态b的时候,又会根据某条件2,跳转到状态a,产生了这样一个跳转的“环”。显而易见的是,行为树是一种树形结构,而带环的状态机是一种图的结构,所以对于这种情况,我想了下,觉得需要引入一种新的选择节点,我称之为动态优先级选择节点(Dynamic Priority Selector),这种选择节点的工作原理是,永远把当前运行的节点作为最低优先级的节点来处理。如下图
当我们在节点a的时候,我们会先判断b的前提,当b的前提满足的时候,我们会运行节点b,下一帧再进来的时候,由于现在运行的是节点b,那它就是最低优先级的,所以,我们会先判断节点a的前提,满足的话,就运行节点a,不满足则继续运行节点b,依次类推。下面是我写的相关代码,可以给大家参考。
1: void DynamicPrioritySelector::Test(const Blackboard& in) const
2: {
3: bool hasRunningChild = IsValid(m_iCurrentRunningChildIndex);
4: int nextRunningChild = -1;
5: for(int i = 0; i < m_ChildNodes.Count(); ++i)
6: {
7: if(hasRunningChild &&
8: m_iCurrentRunningChildIndex == i)
9: {
10: continue;
11: }
12: else
13: {
14: if(m_ChildNodes[i]->Test(in))
15: {
16: nextRunningChild = i;
17: break;
18: }
19: }
20: }
21: if(IsValid(nextRunningChild))
22: {
23: m_iCurrentRunningChildIndex = nextRunningChild;
24: }
25: else
26: {
27: //最后测试当前运行的子节点
28: if(hasRunningChild)
29: {
30: if(!m_ChildNodes[m_iCurrentRunningChildIndex]->Test(in))
31: {
32: m_iCurrentRunningChildIndex = -1;
33: }
34: }
35: }
36: return IsValid(m_iCurrentRunningChildIndex);
37: }
总结
从上面4种模式的转化方式中,我们好像会有种感觉,用行为树的表达好像并没有状态机的表述清晰,显的比较复杂,罗嗦。这主要是因为我们用行为树对状态机做了直接的转化,并想要尽力的去维持状态机的语义的缘故。其实,在AI设计过程中,一般来说,我们并不是先有状态机,再去转化成行为树的,当我们选择用行为树的时候,我们就要充分的理解控制节点,前提,节点等概念,并试着用行为树的逻辑方式去思考和设计。
不过,有时,我们也许想用行为树改造一个已有的状态机系统,那这时就可以用我上面提到的这些模式来尝试着去转换,当然在实际转换的过程中,我的建议是,先理清并列出每一个状态跳转的条件,查看哪些是带上下文的跳转,哪些是不带上下文的跳转,哪些是单纯的序列跳转(比如,从状态A,到状态B,到状态C,类似这样的单线跳转,常见于流程控制中),哪些跳转是可以合并的等等,然后再用行为树的控制节点,把这些状态都串联起来,当发现有些跳转用已有的控制节点不能很好的描述的时候,可以像我上面那样,添加新的控制节点。
这四种模式,是我现在能想到的,可能不全,如果大家有问题,可以在后面留言,有指教的也欢迎一起讨论。
————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————
这是在状态机里最常见的模式,由于是基于条件的跳转,所以可以非常方便的用选择节点S和前提P的组合来描述。对于自跳转,其实就是维持了当前的状态,在构建行为树的时候,不需要考虑转换。如下图,用序列节点保证跳转的上下文,在状态a结束的时候,在黑板中记录其结束的原因,以供后续的选择节点来选择。另外,在第二层S用到了非优先级的选择节点,根据上一层SE的运行结果运行。
和模式2类似的是,可以不用序列节点,只用选择节点,默认状态a就需要处在选择节点的最后,仅当所有跳转条件都不满足的时候,才会维持状态a。如上图,b,c,d的跳转条件包括了前提P和InAXState,InAXState保证前提P处在状态a之中,这样就完成了自跳转。
这段看不下去了,太啰嗦反复,太多代词了,LZ想得明白,但说不清楚,帮你修改了一下
说的很好,我这段时间正好有个儿童智能产品在研发,苦于没有思路,现在有思路了。我会经常关注你。非常感谢!
如果循环跳转里有多个状态(例如三个a->b->c->c),只把当前状态设为最低优先级是否依旧有效?其他状态有优先级吗?谢谢!
这种模式的话,其实就是序列节点,因为他有一个明显的序列关系,不过就像我说的,用状态机去转行为树,不是一个很好的注意,更好的是直接使用行为树的方式去思考
楼主分析的好精彩啊
很棒,谢谢图文并茂分析分享。
很赞啊,支持一把
哇,好就没见你了~