0x00 前言

因为临近年关工作繁忙,已经有一段时间没有更新博客了。到了元旦终于有时间来写点东西,既是积累也是分享。如题目所示,本文要来聊一聊在游戏开发中经常会涉及到的话题——游戏AI。设计游戏AI的目标之一是要找到一种便于使用并容易拓展的的方案,常见的一些游戏AI方案包括了有限状态机(FSM)、分层有限状态机(HFSM)、面向目标的动作规划(GOAP)以及分层任务网络(HTN)和行为树(BT)等等。下面我们就来聊一聊比较有代表性的游戏AI方案——状态机。

0x01 有限状态机(FSM)

有限状态自动机 (Finite State Machine,FSM)是表示有限多个状态以及在这些状态(State)之间转移(Transition)和动作(Action)的数学模型。有限状态机的模型体现了两点:

  1. 状态首先是离散的:某一时刻只能处于某种状态之下,且需要满足某种条件才能从一种状态转移到另一种状态。
  2. 然后状态总数是有限的。

从它的定义,我们可以看到有限状态机的几个重要概念:

  • 状态(State):表示对象的某种形态,在当前形态下可能会拥有不同的行为和属性。
  • 转移(Transition):表示状态变更,并且必须满足确使转移发生的条件来执行。
  • 动作(Action):表示在给定时刻要进行的活动。
  • 事件(Event):事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态。

而状态机便是用来控制对象状态的管理器。在满足了某种条件或者说在某个特定的事件被触发之后,对象的状态便会通过转换来变成另外一种状态,而对象在不同的状态之下也有可能会有不同的行为和属性。
当然,有限状态机的应用范围很广,但是显然游戏开发是有限状态机最为成功的应用领域之一。除了游戏AI的实现可以依靠有限状态机之外,游戏逻辑以及动作切换都可以借助有限状态机来实现。因此游戏中的每个角色或者器件或者逻辑都有可能内嵌一个状态机。

0x02 HFSM分层有限状态机

如果我们仔细观察一个有限状态机的话,可以发现它在逻辑结构上是没有层次的,如果和行为树来做对比的话可以发现这一点十分明显。在行为树中,节点是有层次(Hierarchical)的,子节点由其父节点来控制。例如行为树中有一种节点叫做“序列(Sequence)节点”,它的作用是顺序执行所有子节点(如果某个子节点失败返回失败,否则返回成功)。而将行为树的这个优势应用到有限状态机上,分层有限状态机HFSM便诞生了。

分层的好处

那么引入了分层之后的HFSM到底带来了什么好处呢?
最大的好处便是在一定程度上规范了状态机的状态转换,从而有效地减少了状态之间的转换。
举一个简单的小例子:例如RTS游戏中的士兵。如果逻辑没有层次上的划分,那么我们对士兵所定义的若干状态,例如前进、寻敌、攻击、防御、逃跑等等,就需要在这些状态之间定义转移,因为它们是平级的,因此我们需要考虑每一组状态的关系,并维护一大堆没有侧重点的转移。
如果在逻辑上是分层的,我们就可以将士兵的这些状态进行一个分类,把几个低级的状态归并到一个高级的状态中,并且状态的转移只发生在同级的状态中。
例如高级状态包括战斗、撤退,而战斗状态中又包括了寻敌、攻击等几个小状态;撤退状态中又包括了防御、逃跑这几个小状态。

总而言之,分层状态机HFSM从某种程度上规范了状态机的状态转移,而且状态内的子状态不需要关心外部状态的跳转,这样也做到了无关状态间的隔离。

0x03 有限状态机的实现

那么到底如何实现一个有限状态机呢?主要有两种方式来实现,即集中管理控制以及模块化管理。具体来说,这两种方式的实现如下:

  1. 使用switch语句:所有的状态之间的转移逻辑全都写在一个部分,需要根据不同的分支来判断转移条件是否符合。
  2. 使用状态模式(State Pattern):一种常见的设计模式。在状态模式中,我们为每个状态创建与之对应的类,这样就将状态转移的逻辑从臃肿的switch语句中分散到了各个类中。

了解了有限状态机大体上可以分为这两种实现方式,那么接下来我们就具体来看一看这两种方式是如何实现的。

switch语句

在实现有限状态机时,使用switch语句是最简单同时也是最直接的一种方式。这种方式的基本思路是为状态机中的每一种状态都设置一个case分支,专门用来对该状态进行控制。

上图是一个具体的使用有限状态机实现游戏AI的场景,描述的是一个游戏单位的AI,下面我们就使用switch语句来实现图中的状态机。

switch (state)
{// 处理状态Waiting的分支case State.Waiting: // 执行等待wait();// 检查是否有可以攻击if (canAttack()){// 当前状态转换为AttackingchangeState(State.Attacking);}// 若不可攻击,则检查是否有可以移动else if (canMove()) { // 当前状态转换为MovingchangeState(State.Moving)}break;// 处理状态Moving的分支case State.Moving: // 执行动作movemove();// 检查是否可以攻击敌人if (canAttack()) {// 当前状态转换为AttackingchangeState(State.Attacking);}// 若不可攻击,则检查是否可以等待else if (canWait()) {// 当前状态转换为WaitingchangeState(State.Waiting);}break;// 处理状态Attacking的分支case State.Attacking: // 执行攻击attackattack();// 检查是否可以等待if (canWait()) {// 当前状态转换为WaitingchangeState(State.Waiting);}break;
}

通过这个小例子,我们可以看到使用switch语句实现的有限状态机的确可以很好的运行。不过我们还可以发现这种方式在实现状态之间的转换时,1.检查转换条件以及2.进行状态转换的代码都是混杂在当前的状态分支中来完成的,这样就会导致代码的可读性降低甚至会增加日后的维护成本。
这是因为在每个具体的状态下,都需要检查多个具体的转换条件,对符合条件的还需要转移到新的具体的状态,这样的代码是难以维护的,因为它们需要在具体的情况下处理具体的事物。即便我们将检查转换条件和进行状态转换的代码分别封装成两个专门的函数FuncA(检查转换条件)和FuncB(进行状态转换),switch语句中各个具体状态的代码可能会更加清晰。但是随着逻辑复杂度的增加,FuncA和FuncB这两个函数本身的复杂度可能也会增加,甚至最后变得臃肿不堪。

状态模式

当控制一个对象状态转换的条件表达式过于复杂时,把状态的判断逻辑转移到一系列类当中,可以把复杂的逻辑判断简单化。因此,使用状态模式来实现状态机虽然不如直接使用switch语句来的直接,但是对于状态更易维护也更易拓展。下面我们就来看一看状态模式中的角色:

  1. 上下文环境(Context):它定义了客户程序需要的接口并维护一个具体状态的实例,将与状态相关的操作(1.检查转换条件;2.进行状态转换)交给当前的具体状态对象来处理。
  2. 抽象状态(State):定义一个接口以封装使用上下文环境的的一个特定状态相关的行为。
  3. 具体状态(Concrete State):实现抽象状态定义的接口。
下面,我们就按照这三个角色来实现上一小节图中的状态机吧。
context类

public class Context
{private State state;public Context(State state){this.state = state;}public void Do(){state.CheckAndTran(this);}
}

抽象状态类:

public abstract class State
{public abstract void CheckAndTran(Context context);
}

具体状态类

public class WaitingState : State
{public override void CheckAndTran(Context context){//执行等待动作Wait();//检查是否可以攻击敌人if (canAttack()){// 当前状态转换为Attackingcontext.State = new AttackingState();}// 若不可攻击,则检查是否有可以移动else if (canMove()) { // 当前状态转换为Movingcontext.State = new MovingState();}}
}
...

虽然看似状态模式缓解了使用switch语句那种代码臃肿、可读性维护性差的问题,但是状态模式并非没有自己的缺点。可以看出状态模式的使用必然会增加类和对象的个数,如果使用不当将导致程序结构和代码的混乱。

0x04 褒扬和批判

在游戏开发中使用状态机显然不失为一种不错的选择,首先它的概念并不复杂,其次它的实现也十分简单而直接。但它的缺点却也十分明显,例如难以复用,因为它往往需要根据具体的情况来做出反应,当然当状态机的模型复杂到一定的程度之后,也会带来实现和维护上的困难。如何选择,可能就是一个仁者见仁智者见智的问题了。

趣说游戏AI开发:对状态机的褒扬和批判相关推荐

  1. 趣说游戏AI开发:曼哈顿街角的A*算法

    前言 请叫我标题党!请叫我标题党!请叫我标题党!因为下面的文字既不发生在美国曼哈顿,也不是一个讲述美国梦的故事.相反,这可能只是一篇没有那么枯燥的关于算法的文章.A星算法,这个在游戏寻路开发中难免会用 ...

  2. 趣说游戏AI开发:曼哈顿街角的A*算法 1

    0x00 前言 请叫我标题党!请叫我标题党!请叫我标题党!因为下面的文字既不发生在美国曼哈顿,也不是一个讲述美国梦的故事.相反,这可能只是一篇没有那么枯燥的关于算法的文章.A星算法,这个在游戏寻路开发 ...

  3. 《游戏AI开发指南(基于Lua的人工智能在游戏中的应用)》(Yanlz+Unity+SteamVR+5G+AI+VR云游戏+Lua+人机交互+沙箱+导航+决策树+影响力地图+立钻哥哥+==)

    <游戏AI开发指南(基于Lua的人工智能在游戏中的应用)> <游戏AI开发指南(基于Lua的人工智能在游戏中的应用)> 版本 作者 参与者 完成日期 备注 YanlzAI_Lu ...

  4. 《游戏人工智能》学习笔记2——5 结构化架构:游戏AI开发的常用技巧

    思维导图 文字版: 5 结构化架构:游戏AI开发的常用技巧 本章目的 让读者对游戏AI的全局框架有充分的了解,并针对开发问题提供思路和解决方案 不会说深入具体方法和技术细节,这些内容可以参考文后文献资 ...

  5. python从入门到精通编程汪老师_游戏AI开发从入门到精通:最全游戏AI编程书单...

    闲来无事,把至今出版过的比较知名的游戏 AI 编程书籍整理了一遍,分了包括游戏AI.游AI戏建模.AI设计策划.自然语言编程.人工智能学术类书籍等八个大类,约45本.附上亚马逊的购买连接(国内介绍太少 ...

  6. 多智能体强化学习及其在游戏AI上的应用与展望

    近年来,人工智能技术在很多领域都取得了亮眼成就,并逐步从感知智能向决策智能迈进.强化学习是实现决策智能的重要路径,而现实世界中往往存在着多智能体的交互,也催生了多智能体强化学习的发展.这篇文章主要对多 ...

  7. 游戏AI:只是AI间的游戏,还是游戏的未来?

    前言背景 1. 雅达利的崛起与沉沦 1974年,一个名叫史蒂夫·乔布斯的年轻人来到了雅达利(Atari)公司位于洛思加图斯的总部,拿着一块他朋友沃兹尼亚克做的电路板,手舞足蹈的比划,试图让对方相信这个 ...

  8. 华为云爆出“神器”,助力游戏AI“闯关升级”

    文|智能相对论 作者|陈选滨 游戏并非小众喜好,早已成为一种主流文化.据DFC Intelligence的最新报告显示,截至2020年年中,全球电子游戏用户接近31亿.也就是说全球将近80亿人口,约4 ...

  9. 博弈对抗游戏AI的技术方法的一些总结和思考

    目录 一.总体概况 1.博弈类型和算法 1)团队博弈 2)有限零和博弈 3) CFR系列算法 4)NFSP系列算法 2.不同的决策方式 二.不同游戏AI的简单介绍 1.棋盘游戏AI 2.纸牌游戏AI ...

最新文章

  1. 初级脚本Ubuntu18-20.04版本以上设置静态IP地址
  2. 免费Java高级工程师学习资源,使用指南
  3. Serverless 微服务实践-移动应用包分发服务
  4. Eclipse新建SpringBoot项目,application.properties配置中文乱码
  5. 简单查找,如果找到返回下标,如果找不到返回-1
  6. phpstorm9 增加对.vue的支持
  7. 阿里云高效基因序列检索助力新冠肺炎病毒序列快速分析
  8. 解决Ubuntu vim 中文乱码
  9. C语言课后习题(62)
  10. 论文笔记_S2D.70_2021_IEEE-RAL_CodeMapping:使用紧凑场景表示的稀疏SLAM的实时稠密建图
  11. 16进制 转为图片 php_PHP实现简单RPC
  12. Spring MVC实现服务端数据验证 服务端数据校验 Spring Boot 服务端数据校验
  13. python调用迅雷下载引擎_GitHub - cryzlasm/ThunderOpenSDK: 迅雷开放下载引擎, 收集, 使用...
  14. 楼天城楼教主的acm心路历程
  15. android 自定义textview圆形,Android 自定义TextView可以设置圆角和按下效果
  16. php返回token什么意思,token什么意思
  17. LINUX下更新flash
  18. Markdown快速入门
  19. bzoj1627 / P2873 [USACO07DEC]泥水坑Mud Puddles
  20. win8.1怎样打开计算机名,Win8怎么打开cmd命令窗口_Win8.1打开命令提示符的方法-192路由网...

热门文章

  1. router vue 动态改变url_2020年 vue常见面试问题总结(干货)!
  2. qc成果报告范例_质量引领创新 扬子江勇夺医药行业QC“十五连冠”
  3. idea测试连接mysql报错08001_IDEA连接MySQL(版本8)数据库失败的解决方法(报错08001)...
  4. php有哪些高级扩展,php扩展有哪些
  5. php程序员学什么语言好就业_2019年初中毕业生学什么专业好?学什么专业好就业?...
  6. 一个搜索框多个按钮_网站搜索栏设计指南:要不要?怎么设计?
  7. linux内核bios,BIOS的启动原理——Linux内核设计学习笔记
  8. 排序千万级数据_从千万级房产成交量排名,窥探中国城市的真实家底
  9. append 后如何删除_如何在STATA中合并数据文件呢?
  10. php 串口通信例程,HAL库串口通信例程