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

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游戏中的士兵。如果逻辑没有层次上的划分,那么我们对士兵所定义的若干状态,例如前进、寻敌、攻击、防御、逃跑等等,就需要在这些状态之间定义转移,因为它们是平级的,因此我们需要考虑每一组状态的关系,并维护一大堆没有侧重点的转移。
如果在逻辑上是分层的,我们就可以将士兵的这些状态进行一个分类,把几个低级的状态归并到一个高级的状态中,并且状态的转移只发生在同级的状态中。
例如高级状态包括战斗、撤退,而战斗状态中又包括了寻敌、攻击等几个小状态;撤退状态中又包括了防御、逃跑这几个小状态。
686199-20160103232925339-1051063527.png
总而言之,分层状态机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 褒扬和批判

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/543780.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

类中函数模板 typeof_Julia中的typeof()函数

类中函数模板 typeofJulia| typeof()函数 (Julia | typeof() function) typeof() function is a library function in Julia programming language, it is used to get the concrete type of the given value or variable. typeof()函数是Julia编程语言中的库函数,…

sencha touch调试时Please close other application using ADB: Monitor, DDMS, Eclipse

1、运行——cmd—— netstat -aon|findstr "5037" 2、打开任务管理器,查看所有进程 显示进程pid(文件-查看)--查找pid7740的结束。转载于:https://www.cnblogs.com/taoshengyujiu/p/5099588.html

定时器mia是什么意思_MIA的完整形式是什么?

定时器mia是什么意思MIA:行动失踪 (MIA: Missing In Action) MIA is an abbreviation of "Missing In Action". MIA是“缺少行动”的缩写。 It is an expression, which is commonly used in the Gmail platform. It is written to show that the origin…

window下自己主动备份数据库成dmp格式的bat写法

复制以下的命令到一个txt文本文档&#xff0c;然后改动相应的參数为自己须要的參数&#xff0c;一切完毕之后&#xff0c;将文件保存为bat格式。这样每次须要备份的时候仅仅须要双击一下这个bat文件。其它的都不用你了&#xff0c;你仅仅须要静静的等待……</pre><pre…

l和l_L&T的完整形式是什么?

l和&lL&#xff06;T&#xff1a;Larsen和Toubro (L&T: Larsen and Toubro) L&T is an abbreviation of Larsen and Toubro. It is an Indian multinational conglomerate corporation with international networks and operations. It is dynamically engaged in …

例题 3-5 生成元 digit generator

1 #include<stdio.h>2 #include<string.h>3 #define maxn 1000054 int ans[maxn]; //类似于 比较大的数组还是开导外面比较好一点,防止报错.5 int main()6 {7 int x,y,m,T,n;8 memset(ans,0,sizeof(ans)); //数组归零.9 for(m1;m<maxn;m…

CRT的完整形式是什么?

CRT&#xff1a;阴极射线管 (CRT: Cathode Ray Tube) CRT is an abbreviation of Cathode Ray Tube. Cathode Ray Tube is a vacuum tube that accommodates one or more than one electron filled guns and a phosphorescent screen, which is used in television and convent…

Python打包程序

到py2exe的官网下载程序&#xff0c;注意对应的python版本&#xff0c;比如所用的python版本为2.7&#xff0c;那么就下载适配2.7版本的py2exe软件下载完成后安装&#xff08;与普通软件安装方式相同&#xff09; 2将要转换的python脚本放到Python文件夹内 在python的目录下面&…

ruby中!!_Ruby反向! 功能

ruby中!!逆转&#xff01; Ruby中的功能 (reverse! function in Ruby) As the name suggests, reverse! function is used to reverse the elements of an array. Most of the times, we need to reverse an array but if we do it with the help of loops, the program will b…

mcal rtm_RTM的完整形式是什么?

mcal rtmRTM&#xff1a;阅读手册 (RTM: Read The Manual) RTM is an abbreviation of "Read The Manual". RTM是“阅读本手册”的缩写 。 It is an expression, which is commonly used in messaging or chatting on social media networking sites like Facebook,…

mysql悲观锁总结和实践

使用场景举例&#xff1a;以MySQL InnoDB为例商品t_goods表中有一个字段status&#xff0c;status为1代表商品未被下单&#xff0c;status为2代表商品已经被下单&#xff0c;那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。 一、如果不采用锁&#xff0c;…

2G的完整形式是什么?

2G&#xff1a;第二代 (2G: Second Generation) 2G is an abbreviation of the "Second-Generation Cellular Network". 2G是“第二代蜂窝网络”的缩写 。 In 1991, 2G cellular networks were commercially introduced on the GSM standard in Finland by Radiolin…

推送证书

2019独角兽企业重金招聘Python工程师标准>>> 推送证书 1 openssl pkcs12 -in CertificateName.p12 -out CertificateName.pem -nodes 转换文件上传 2证书有效期 openssl x509 -in xxx.pem -noout -dates —反馈 notBeforeDec 12 07:42:27 2015 GMT notAfterDec 11…

ruby 覆盖率测试_Ruby方法覆盖

ruby 覆盖率测试Ruby中的方法重写 (Method overriding in Ruby) Method overriding simply means that there are two methods defined within the same scope and they both are used for performing different tasks. This feature is provided in an Object-oriented langua…

iOS 之 UITextField

UITextField 之 失去焦点 收起键盘 UITextField 之 手势收起键盘转载于:https://www.cnblogs.com/SimonGao/p/5106681.html

ruby推送示例_Ruby直到示例循环

ruby推送示例直到循环 (The until loop) The until loop is one of the great features of Ruby which makes it different from other programming languages. The support of until loop specifies how much user-friendly language Ruby is? 直到循环是Ruby的重要功能之一&…

Dubbo学习总结(4)——Dubbo基于Zookeeper实现分布式实例

入门实例解析 第一&#xff1a;provider-提供服务和相应的接口 创建DemoService接口 [java] view plaincopyprint? <span style"font-size:18px;">package com.unj.dubbotest.provider; import java.util.List; /** * 定义服务接口&#xff0c;该…

什么是5g全双工模式_5G的完整形式是什么?

什么是5g全双工模式5G&#xff1a;第五代 (5G: Fifth Generation) 5G is an abbreviation of the "Fifth Generation". 5G是“第五代”的缩写 。 It is the fifth-generation wireless technology for digital cellular networks that started broad operation in 2…

Gmap.net 怎么导入离线地图

我使用【http://www.cnblogs.com/enjoyeclipse/archive/2013/01/29/2882254.html】所提供的方式导出地图数据 但是在【C:\Users\用户名<你的计算机用户名>\AppData\Local\GMap.NET\TileDBv5\en】这个文件夹下看到的Data.gmdb永远是256m 用这个Data.gmdb文件也无法导入 请…

下一个全排列_下一个排列

下一个全排列Problem statement: 问题陈述&#xff1a; Given a permutation print permutation just greater than this. 给定一个排列&#xff0c;打印排列就比这个更大。 Example: 例&#xff1a; Permutation:1 3 2 5 4Output:1 3 4 2 5Solution: 解&#xff1a; What …