本节书摘来自华章出版社《Unity 3D人工智能编程》一 书中的第2章,第2.6节,作者:(美)基奥(Kyaw,A.S.),(美)彼得斯(Peters,C.),(美)斯瑞(Swe,T.N.),更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.6 使用有限状态机框架
我们要在这里用的有限状态机框架是一个改编过的C#框架,你可以在unifycommunity.com找到它。该框架是确定有限状态机框架的一部分,基于Eric Dybsend 所著的《Game Programming Gems 1》。在这里我们只关注这个有限状态机和我们之前的有限状态机的不同点。这个有限状态机的完整版可以在这本书附带的资源中找到。我们现在将要学习这个框架是如何工作的,并学习如何用它来实现我们的人工智能。
AdvanceFSM 和 FSMState是框架的两个主要的类。接下来我们看看它们吧。
2.6.1 AdvanceFSM类
AdvanceFSM类主要管理所有实现了的FSMState,并不断更新转换和当前状态。因此,在我们使用这个框架前,首先要做的事情就是声明我们的人工智能坦克计划实现的转换和状态。
AdvancedFSM.cs文件中的代码如下所示:

它有一个列表对象来存储FSMState对象,还有两个局部变量来存储FSMState类的当前ID,以及FSMState本身。

AddFSMState方法和DeleteState方法分别用来添加与删除列表中FSMState类的实体。在调用PerformTransition方法时,它会根据相应的转移来更新CurrentState 变量的状态。
2.6.2 FSMState类
FSMState类负责管理所有到其他状态的转移。它有一个叫做map的字典对象来存储所有转移和状态的键值对。例如 ,SawPlayer转移对应到Chasing状态,LostPlayer转移对应到Patrolling状态,等等。
FSMState.cs文件中的代码如下所示:


AddTransition和DeleteTransition方法仅仅从它的状态–转换字典对象map中进行添加和删除。GetOutputState方法则从map对象中查找,并基于输入转换来返回状态。
该FSMState类还声明了两个抽象方法,它的子类需要实现这两个方法。如下所示:
Reason方法要检查当前状态是否需要转换到另一个状态。而Act方法则为currentState变量执行实际的任务,比如向目标点移动,然后追逐并攻击玩家。这两个方法都需要转换玩家和NPC实体的数据,而这些都可以通过FSMState类来获得。
2.6.3 状态类
与我们之前介绍的SimpleFSM例子不同,我们坦克的当前状态分别写入了继承自FSMState类的独立的类,比如AttackState、ChaseState、DeadState和PatrolState ,每个类都实现了Reason和Act方法。我们把PatrolState类拿出来,作为例子研究一下。
PatrolState类
PatrolState类有三个方法:构造函数、Reason方法和Act方法。PatrolState.cs文件中的代码如下所示:


构造函数方法将航点数组作为参数,并把它们存储在一个本地数组中,然后初始化它们的属性,如运动和旋转速度等。Reason方法检查它本身(人工智能坦克)和玩家坦克之间的距离。如果玩家坦克在一定范围内,它将使用NPCTankController类的SetTransition 方法,把转换ID设置为SawPlayer转移,如下所示:
NPCTankController.cs文件中的代码如下:

它只是一个调用了AdvanceFSM类的PerformTransition方法的包装方法。此方法将CurrentState 变量更新为能响应Transition对象所对应的状态,并更新FSMState类中的状态–转移字典对象map。Act方法仅仅更新人工智能坦克的目标点,将坦克向目标点的方向旋转,然后向前移动。其他的状态类同样遵循这个模板,但是使用不同的reason和act程序。我们已经在前面简单的有限状态机例子中了解过它们,此处不再赘述了。看看你自己是否能够成功地设置这些类。如果在这个过程中遇到了困难,你可以在本书附带的资源中查看需要的代码。
2.6.4 NPCTankController 类
在我们的人工智能坦克中,NPCTankController类将继承自AdvanceFSM。以下是我们设置NPC坦克状态的类的方法:


下面是我们的有限状态机框架美妙的地方。因为状态都是在相应的类中自我管理的,所以我们的NPCTankController 类只需要对当前工作的状态调用Reason方法和Act方法,这消除了我们写一长串if/else和switch语句的需要,即消除了臃肿的代码。现在取而代之的是,我们的状态恰好在各自的类中包装好。在更大型的项目中,随着状态数量的增长,以及状态之间的转换变得越来越复杂,我们的代码也将更加易于管理。

我们的框架就是这样工作的。简要地说,使用这个框架的主要步骤如下:
1)在AdvanceFSM类中声明转移和状态。
2)编写继承自FSMState类的状态类,并且实现Reason方法和Act方法。
3)编写继承自AdvanceFSM类的自定义的NPC人工智能类。
4)从State类创建状态,然后用FSMState类的AddTransition 方法将它们添加至转移和状态关联关系中。
5)使用AddFSMState方法将这些状态添加到AdvanceFSM类的状态列表中。
6)在游戏更新周期中,调用CurrentState变量的Reason方法和Act方法。
你可以在Unity中运行一下AdvancedFSM.scene。它将会以与前面的SimpleFSM例子一样的方式运行。但是现在的代码和类将变得更有条理且更易于管理。