基础 | 简单的智能体----有限状态机(七)

本系列为笔者初学c/c++和游戏AI开发的学习经历,练习为主,不涉及到具体的游戏开发软件学习(如unity,虚幻4等),适合刚入门的小伙伴一起学习探讨,欢迎在评论区留下意见。
开发语言:c/c++ (11及以上)
开发平台:macOS mojave / Linux
编译器:vs Code / g++

三、代码实现
3.4 状态类

3.4.1 抽象状态类
在状态模式中(图5),状态被解耦为抽象状态类与具体状态类,定义和实现被分离。
State类为抽象类,是作为接口来使用的,这个接口中封装了每个具体状态类会使用到的一些方法,但并没有具体的实现细节。
接口是一个共享框架,供两个系统交互时使用。在C++中,接口是使用抽象类来实现的。
----《C++ Primer Plus》
使用抽象类来实现接口有许多好处,比如减少重复的代码,提供标准化的管理,定义与具体实现的解耦等等。
在“Bob的一天”中,我们的State类就起到了管理具体状态类的作用,它只是声明了状态类必须含有哪些方法,具体这些方法中实现的细节和差异,则根据不同的状态来做决定。
Enter(Miner *): 进入某一个状态
Execute(Miner *): Miner类在每个时刻具体要执行的动作
Exit(Miner* ): 退出某一个状态

注意我们的抽象类是通过虚函数和纯虚函数来实现的,这意味着每个纯虚函数必须在派生类中被实现。
3.4.1 具体状态类
具体状态类继承了接口的方法,然后根据每个状态的不同来做不同的实现。
如“到矿场挖矿”状态中,有“走到矿场”、“挖矿”和“离开矿场”这三个动作,“挖矿”这个动作中又包含了“是否口渴”、“口袋是否装满了金子”等状态的更新逻辑判断。
而其他三个状态,又各有差异和区别。但不管有多少差异,都一定包含有Enter、Execute和Exit这三个方法。也就是每种状态都有的共性、标准。
以EnterMineAndDigForNugget()这个状态为例,我们通过单例模式来实现具体状态类,关于单例模式我们之前已经介绍过了(详情请见链接)。

该状态类首先继承了Sate类,之后以单例模式来规定该状态类只允许有一个实例化对象,且在退出该状态类后,会销毁并释放其占有的内存。
我们以Instange()函数来返回一个实例化对象,并用其他三个函数来更新Miner的状态。

每个函数体我们都传入一个Miner对象,Miner里具体实现了每个状态中可以执行的动作。如“计算口袋的金子”、“判断是否口渴”、“判断是否疲劳”等等。
可以见到,在整个项目当中,我们具体的更新状态的逻辑,也就是Bob会做的动作,都是在具体状态类中去实现的。
Miner类只是定义了一个Miner可以会有那些属性和动作,并写好它们的实现,但是具体的使用逻辑则交给具体状态类根据具体的使用场景去决定。
这样做的好处是,如果我们之后想要给Bob增加新的状态,只需要在Miner类中新增一些动作相关函数,然后再新建一个具体状态类,在里面去调用、去使用这些函数就可以。
我们不用每次都写这些重复的代码,而且可以很方便的增删管理。
比如在“到矿场挖矿”这个状态中,我们要Bob也有“去睡觉休息”这个动作,只需要加入几行代码即可:

3.5 主函数入口
当然,C++中我们的程序都必须有一个入口,作为一切的开始。

四、结束语
这样,一个简单的有限状态机实现的智能体Bob就完成了,在小镇上的一天中,他挖矿、喝酒和睡觉,知道自己什么时候会累,会渴,也知道自己银行账户的余额。
Bob有简单的外界感知能力,虽然都是我们人工赋予他的,但这个简单的项目可以作为我们入门游戏人工智能这个有趣的领域。
也许很多年以后,游戏中的NPC也有着复杂的情感,也会有自己的“生活”。

知识点:
抽象类&接口

参考:
《游戏人工智能编程案例精粹》
《C++ Primer Plus》
相关代码下载:https://github.com/linpeijie/GameToy/tree/master/GameAI/FSM