欢迎光临散文网 会员登陆 & 注册

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

2020-03-11 12:56 作者:有木乘舟  | 我要投稿

本系列为笔者初学c/c++和游戏AI开发的学习经历,练习为主,不涉及到具体的游戏开发软件学习(如unity,虚幻4等),适合刚入门的小伙伴一起学习探讨,欢迎在评论区留下意见。

  • 开发语言:c/c++ (11及以上) 

  • 开发平台:macOS mojave / Linux 

  • 编译器:vs Code / g++

三、代码实现

3.4 状态类

图5 “Bob的一天”状态机实现的UML图

3.4.1 抽象状态类

  在状态模式中(图5),状态被解耦为抽象状态类具体状态类,定义和实现被分离

  State类为抽象类,是作为接口来使用的,这个接口中封装了每个具体状态类会使用到的一些方法,但并没有具体的实现细节。

接口是一个共享框架,供两个系统交互时使用。在C++中,接口是使用抽象类来实现的。

----《C++ Primer Plus》

  使用抽象类来实现接口有许多好处,比如减少重复的代码,提供标准化的管理,定义与具体实现的解耦等等。

  在“Bob的一天”中,我们的State类就起到了管理具体状态类的作用,它只是声明了状态类必须含有哪些方法,具体这些方法中实现的细节和差异,则根据不同的状态来做决定。

  • Enter(Miner *): 进入某一个状态

  • Execute(Miner *): Miner类在每个时刻具体要执行的动作

  • Exit(Miner* ): 退出某一个状态

图15 State类

  注意我们的抽象类是通过虚函数纯虚函数来实现的,这意味着每个纯虚函数必须在派生类中被实现。

3.4.1 具体状态类

  具体状态类继承了接口的方法,然后根据每个状态的不同来做不同的实现。

  如“到矿场挖矿”状态中,有“走到矿场”、“挖矿”和“离开矿场”这三个动作,“挖矿”这个动作中又包含了“是否口渴”、“口袋是否装满了金子”等状态的更新逻辑判断。

  而其他三个状态,又各有差异和区别。但不管有多少差异,都一定包含有Enter、Execute和Exit这三个方法。也就是每种状态都有的共性、标准

  以EnterMineAndDigForNugget()这个状态为例,我们通过单例模式来实现具体状态类,关于单例模式我们之前已经介绍过了(详情请见链接)。

图16 “到矿场挖矿”状态类

  该状态类首先继承了Sate类,之后以单例模式来规定该状态类只允许有一个实例化对象,且在退出该状态类后,会销毁并释放其占有的内存。

  我们以Instange()函数来返回一个实例化对象,并用其他三个函数来更新Miner的状态。

图17 具体状态类的实现

  每个函数体我们都传入一个Miner对象,Miner里具体实现了每个状态中可以执行的动作。如“计算口袋的金子”、“判断是否口渴”、“判断是否疲劳”等等。

  可以见到,在整个项目当中,我们具体的更新状态的逻辑,也就是Bob会做的动作,都是在具体状态类中去实现的。

  Miner类只是定义了一个Miner可以会有那些属性和动作,并写好它们的实现,但是具体的使用逻辑则交给具体状态类根据具体的使用场景去决定。

  这样做的好处是,如果我们之后想要给Bob增加新的状态,只需要在Miner类中新增一些动作相关函数,然后再新建一个具体状态类,在里面去调用、去使用这些函数就可以。

  我们不用每次都写这些重复的代码,而且可以很方便的增删管理。

  比如在“到矿场挖矿”这个状态中,我们要Bob也有“去睡觉休息”这个动作,只需要加入几行代码即可:

图18 Bob累了就回家睡觉

3.5 主函数入口  

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

图19 主函数

  四、结束语

  这样,一个简单的有限状态机实现的智能体Bob就完成了,在小镇上的一天中,他挖矿、喝酒和睡觉,知道自己什么时候会累,会渴,也知道自己银行账户的余额。

  Bob有简单的外界感知能力,虽然都是我们人工赋予他的,但这个简单的项目可以作为我们入门游戏人工智能这个有趣的领域。

  也许很多年以后,游戏中的NPC也有着复杂的情感,也会有自己的“生活”。

知识点:

  • 抽象类&接口

参考: 

  • 《游戏人工智能编程案例精粹》

  • 《C++ Primer Plus》 

相关代码下载:https://github.com/linpeijie/GameToy/tree/master/GameAI/FSM


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

分享到微博请遵守国家法律