有限状态机的一般实现是将每个状态写成类,再用一个载体(也就是所谓的状态机)管理这些状态的切换。
关于状态机设计模式介绍,可参考我的另一篇博文:https://www.cnblogs.com/KillerAery/p/9680303.html
在以前,游戏AI的实现基本都是有限状态机,
随着游戏的进步,游戏AI的复杂性要求越来越高,传统的有限状态机实现很难维护越来越复杂的AI需求。
有限状态机的缺陷:
(一个武装小队队员的AI行为树示例)
控制节点是用于控制如何执行子节点。它提供(执行成功/执行失败)两种执行结果。
下面列出一些控制节点的介绍:
按顺序执行多个子节点,若成功执行一个子节点,则不继续执行下一个子节点。
举例:实现要不攻击,要不防御,要不逃跑。
用一个选择节点,按顺序添加<攻击节点>和<防御节点>和<逃跑节点>作为子节点。
按顺序执行多个子节点,若遇到一个子节点不能执行,则不继续执行下一个子节点。
举例:实现先开门再移动到房子里。
用一个顺序节点,按顺序添加<开门节点>和<移动节点>作为子节点。
同时执行多个节点。
举例:一边说话和一边走路。
用一个并行节点,添加<说话节点>和<走路节点>作为子节点。
常用的控制节点一般是<并行节点><选择节点><并行节点>。当然还有其他更多控制节点种类(不常用):
可能到这里,有想到还有个问题:为什么控制节点也需要提供(执行成功/执行失败)两种执行结果。
答:这样做就可以做到决策的复合——控制节点不仅可以控制行为节点,也能控制控制节点。
可以规定:对于控制节点,如果它存在任意一个子节点执行成功,则它执行成功。否则,它执行失败。
行为节点是代表行为的叶节点,当被遍历(执行)时则执行该节点代表的行为。它提供(执行成功/执行失败)两种执行结果。
行为节点的类型是比较多的,毕竟一个智能体的行为是多种多样的,而且都得根据自己的智能体模型定制行为节点类型。
这里列举一些行为:站立,射击,移动,跟随,远离,保持距离....
执行行为不会总是一帆风顺的,有成功也总会有失败的结果。
这就是引入前提条件的作用——满足前提条件,才能成功执行行为,返还<执行成功>结果。否则不能执行行为,返还<执行失败>结果。
但是每个行为的前提总会不同,或有些甚至没有前提(换句话说,前提条件总是能满足)
一个可行的做法是:让行为节点含有bool函数对象(或函数接口)。这样对于不同的逻辑条件,就可以写成不同的bool函数,绑定给相应的行为节点。
另一种更复杂的做法则是把前提条件抽象分离成新的节点类型,称之为条件节点。将其作为叶节点混入行为树,辅助控制节点决策。它相当模块化,脚本性能更高。但是由于逻辑条件的种类繁多,其编写各种条件节点类需要花费一定时间。
一些行为是可以瞬间执行完的(例如转身?),
而另外一些动作则是执行持续一段时间才能完成的(例如攻击从启动攻击行为到攻击结算要1秒左右的时间)
为了不让每帧重复启动执行一个持续行为,
我们给所有行为节点引入一个成员变量来标志,我们称为<行为状态>。
行为状态一般有2种:
这样提供执行结果前就要考虑这些行为状态了:
另外可根据自己实际项目需求来定制状态(例如加入fail状态)。
暂时懒得写。
对于Unity用户,Unity商店现在已经有一个比较完善的行为树设计插件可供购买使用。
到这里,我们可以看到行为树的本质:
相比较传统的有限状态机:
这里并不是说有限状态机一无所用:
简言之:行为树是适合解决复杂AI的解决方案。
可让根节点记录该AI要操控的智能体引用(指针),每次进行决策,传给子节点当前要操控的智能体引用。这样就可以使AI行为树容易改变寄主。
(例如1个丧尸死了被释放内存了,寄生它的AI行为树不必释放并标记为可用。一旦产生新的丧尸,就可以给这个行为树根节点更换新的寄主,标记再改回来)
控制节点里有一种次数限制节点,意思就是执行过N次行为后,就不再执行。得益于树状结构,可以给次数限制节点搞执行N次后,解开与父节点的连接,释放自己以及自己的子节点。这是种‘失忆‘的小技巧。
共享节点型行为树是可供多个智能体共用的一种行为树,是节省内存的一种设计:http://www.aisharing.com/archives/563
原文:https://www.cnblogs.com/KillerAery/p/10007887.html