dsware游戏怎么玩
有限状态机在游戏制作中十分常见,它既可以作为玩家角色的控制框架,纯代码控制动画的播放,免去动画间的“连连看”;也可以制作简单的AI,甚至还可以搭配其它AI决策方式做出更复杂易用的AI控制……本文仅是个人对有限状态机的理解,与大家一同交流有限状态机的使用。
有限状态机(finite-state machine,缩写:FSM),本身是一种数学计算模型,用于有限几个「状态」的动作与它们之间的转换。大概长这样:
此物在Unity中亦有记载——那就是动画控制器,它也是一种有限状态机,只不过各个状态都是动画片段,它们之间的转化的条件是参数。
一个状态机中,只能同时处于一个状态,而下一个状态只能从当前状态转换。同时,一个状态中不能用相同条件转移到不同状态,因为这样违背了这点,例如下面这样:
「状态」并不是具体的,只要你有办法定义,它可以是别的任何东西;而状态转换的条件更是可以小到变量、大到函数。
有限状态机有个非常重要的特点:,这就使得控制的逻辑变得清晰。游戏开发中,我们就可以将角色的一个行为作为一种「状态」,一些条件判断作为转换的依据。
首先我们定义有限状态机中的「状态」,如前文所言,「状态」可以是很多东西,但通常都少不了以下内容:
进入该状态时会执行一次的逻辑
处于该状态时会不断执行的逻辑
退出该状态(转移到其它状态)时会执行一次的逻辑
故而,我们可以这样将它们以接口的方式定义:
只要继承了这个接口,就可以作为一种「状态」。什么?你说你的角色还会用到、 等其它的「不断更新」的函数,该如何在「状态」中增加这些逻辑?
其实我们所写的虽为接口,但并不能直接作为根本,我是说具体状态并非是直接继承这个接口实现的,考虑到实际中,所谓可能不止一种,所以我们要用一个继承了这个接口的类作为基类状态(在「示例」部分会展示这一点)。
我们并不需要对转换条件单独写一个类,转换条件可以直接写在诸如 这类函数中,自行判断切换(示例中有体现)。
状态机的设计需要考虑以下问题:
能方便地增加与查找各个状态
能方便的切换状态
能很好地执行状态的逻辑(即状态进入、退出、持续执行的那些逻辑)
对于第一个问题,我们可以使用字典存储状态,这样就方便增加与查找。但该用什么作为字典的键值呢?首先,我们知道状态机中的各个状态是没有重复的(两个相同的状态也没什么意义好吧),或许可以给各个状态起个名字用作键值,当然也可以自定义枚举变量。但这些都要额外多些变量,莫不如就用状态本身的类型(System.Type),故而我们可以这么写:
接着,该看看如何切换了。已知状态机时刻只能处以一个状态,那么我们就定义一个「当前状态」,切换便是这个变量的变化:
假设有个状态类叫 且已经添加到状态表里了,那么要从当前状态切换到 ,就直接这样调用即可:
最后,我们的状态机还必须具备处理当前状态逻辑的能力。
首先是比较特殊的进入、退出逻辑,它们都是在特殊时刻执行一次。这并不难,在状态机切换状态时处理下即可——在切换时,当前状态触发「退出」逻辑、新的状态触发「进入」逻辑:
接下来便是那些需要「不断执行」的逻辑了,其实就是一个包装,我们只需调用状态机的就能让「当前状态」的对应逻辑调用了。
总结上述内容,一个完整的状态机类如下所示:
也许你心中还有一些疑问,看我猜的准不准:
为什么状态机是作为普通的类,而不是继承?
合情合理的问题 (我自己也用过继承的状态机,毕竟 想要不断执行,也要在Unity生命周期函数中的 里调用。那还不如直接继承 ,这样直接在 中调用 。而不这么做是因为:如果一个物体挂载了这样一个继承了 的状态机,那它就只能是一个状态机了。
大家应该都知道,中的动画状态机是分层级,这使得角色的各个部位可以执行不同的动画。例如,下半身播放行走动画,上半身播放射击动画,从而做到边射击边移动。考虑到可能需要一个脚本中使用多个状态机,故而将它作为普通的类。
状态有很多持续执行的逻辑,但并不是都适合在中调用怎么办?
这个也和之前设计「状态」时的做法一样,我们实现的这个 也并非直接使用,最妥当的做法还是根据「状态」进行继承扩充,例如,我的状态设计 动画IK,有些需要在生命周期中的 调用的逻辑,我们就可以这样继承:
项目链接:https://gitee.com/OwlCat/some-projects-in-tutorials/tree/master/FSM
我们实现以下这样的行为切换规则用以实践有限状态机:玩家在站立时,可切换到下蹲或跳跃(落地后站立);在下蹲后会一直蹲着,触发主动站起来;蹲着时不能跳跃,且可以选择挥拳;当玩家挥拳时可以选择停止,且如果不是蹲着就不能挥拳。
这可以用两个状态机表示,一个控制大动作间的切换,一个负责手臂动作的切换:
首先我们定义一个挂载在角色身上用于控制的 脚本,它包含一个控制动画的动画机,以及先前提到的两个有限状态机;还有几个属性读取按键状态,控制状态的转换条件的触发:
接着,定义玩家状态基类,如前所述它将继承 接口,而由于每个状态都有对应的动画要播放,故而我们可以为每个状态都配备一个动画名字或动画哈希,以便进入到该状态时,用动画机播放。这其实有点像代码控制了Unity动画控制器,只不过附带了些额外逻辑。这是比较常见的做法,使得我们省去了动画机中各个动画切换间的连线。
然后是玩家状态机,完成目前的任务并不需要额外函数,但考虑到手臂的状态切换条件与大动作有关,所以我们将 即「当前状态」用属性的方式公开,方便读取状态机的当前状态:
一切准备就绪,可以实现具体状态了:
视为「站立」
视为「跳跃」
视为「下蹲」
视为「蹲着」
视为「起立」
视为「无事」
视为「挥拳」
先来看看「站立」,根据需求,站立可以转换成两种状态——蹲下与跳跃:
再来看看「蹲下」,下蹲只可以转换成「蹲着」,而且理应是蹲下动画播放完成后就变为「蹲着」:
注意,由于是使用 混合过渡动画,所以只是判断当前播放进度归一化时间还不够,还需确认当前动画名字或哈希是否与需要转换到的动画匹配。
因为没有其它逻辑,所以其余的状态都与这两个相差不大:
接下来便是第二个状态机了,也一样简单,只不过要注意,此时控制的应当是 而且动画机的 或 应当用于层级1而非默认的层级0:
最后,在 中为两个状态机,添加各自状态:
这些动画名字当然是根据动画机里的:
最终效果符合预期:
FSM_0
FSM_1
目前我们主要讨论的是纯粹使用有限状态机在角色控制上的应用,其实它也很容易与其它决策方式进行融合。以 为例, 可以为角色AI规划出未来的行为序列并逐一执行,但在实际执行时,也常会因外部原因而中断。
例如,规划出了一个小兵的行动为:前往兵器库,拾取武器,返回城墙,巡逻。但鉴于小兵是比较低级的怪,如果受到攻击,无论他在执行上述哪一部,都应当打断并重新规划。这样就必须在每次执行前的条件中添加“没有受伤”:
而一想到很多的行为其实在受到攻击时都应当被打断,这样添加额外条件判断属实繁琐。当然,这时纯粹用决策时的问题,我们而将有限状态机与 结合的话就简单很多了,结构如下:
非常小巧的有限状态机,但能将这种意外的中断从中分离出来。类似的构思其实也不少,像首个使用了 作为敌人AI的游戏《F.E.A.R》,他们是用 规划出合适的行为序列,再交给有限状态机去执行行为。
有限状态机是比较基础的行为决策方式,但又不限于行为决策,像游戏进程的控制,开始游戏,暂停游戏,退出游戏,重来游戏……也可以视为一个个状态并用状态机管理。只要能将问题抽象成状态间的转换,都可以尝试用有限状态机解决,会使得逻辑更加清晰。更多用法还得从实践中去学习啦!