好不容易熬完期末周
不想睡觉,
半夜正在折腾arch服务器…
Capoo半夜突然出阳台来看雪
于是我出阳台来看Capoo
冷🥶

好不容易熬完期末周
不想睡觉,
半夜正在折腾arch服务器…
Capoo半夜突然出阳台来看雪
于是我出阳台来看Capoo
冷🥶

祝生物钟倒转的三天新年快乐🎆
基于上次的状态机:

之前说过我当时写出这个状态机时,是从最初的if-else重构过来的。
所以当时新接触这个状态机时,我也尝试过从if-else的角度去反向理解这个教简单的状态机,然后误打误撞就发现这其实就是分层状态机(HFSM)的思想

事实上把陌生和友好两个状态合并起来就是对应着本的’false’,而逃跑状态则对应原本的’true’
或者换句话说: if-else本身就是一种广义上的二元状态机
同时,在我们的这个例子中,我们用抽象的思维看,其实’陌生和友好’本身也是一个二元的状态机,它作为一个状态机被封装成了大的if-else二元状态机下的一个状态。
也就是说:
状态机中的状态可以是另一个状态机
这也就是分层状态机HFSM
我们把之前的FSM重构为HFSM,它总体上大致长这样
hfsm的运行逻辑是先进入父状态机,然后根据过渡条件选择对应的子状态机,再运行子状态机并进入子状态机下的状态。
各层级内需要做好抽象屏障,子状态机的状态不应与父状态机的状态存在任何通信。
以下是HFSM的一个通用模型

对于我们这个例子:
父状态机是‘逃跑-不逃跑’,存在的子状态机是‘陌生-友好’。
在由原先的FSM重构为HFSM的过程中,
状态之间原本都是直接通信
为了防止破坏层级抽象
实际上对于这个例子具体实现上需要一点特殊处理

即通过外部的一个中转Boolean来让‘不逃跑’跳转到‘逃跑’,而不是直接从‘友好’或者‘陌生’跳转到‘逃跑’。
当然,需要这样去操作也说明在构建hfsm时‘友好’和‘陌生’就不适合被分到同一状态机下,而且这个例子其实也没必要使用HFSM,只是为了从FSM引出HFSM而已。
HFSM真正的适用场景是在FSM的状态数量已经达到非常多时,难以管理,遂把各个小状态依据关联性分类并组装成一个个子状态机,然后嵌入到父状态机中,可以进行很多层的封装。
首先,对于大量子状态的判定会在这种boolean的判定中越来越冗长,如果是写了辅助方法来包装则会有层层嵌套的问题,而且对于几个不相关的子boolean强行包装成一个辅助boolean会导致可读性下降,最终整体的状态判定会很难维护
同时,最最危险的一点在于if-else的各种子状态判定一旦存在耦合,会变得非常非常麻烦
举个例子,我们判断一只羊是否应该逃跑(记为isFleeing), 假设isFleeing这个boolean由两个子状态A和B来判定:
A 代表空间判定(包含视角判定,空间距离判定)
B 代表食物引诱判定(即羊不应在受吸引时逃跑)
或许这个时候你会想这么写
1 | if (A && B) { |
然而事实是B中实际上嵌套了A中的部分空间逻辑🤓,这意味着 A 与 B 并不是两个独立条件,这就非常头疼了
通常接下来你有两种常规的处理这玩意的思路:
第一种是打补丁:在A中嵌套B联合一些其他的空间状态判定,从而在耦合的那块地方强行用B覆盖A(假如决策系统稍微再复杂一点点甚至会需要A与B互相嵌套)。
最终A与B之间的耦合度越来越高导致会严重破坏抽象,可以想象调试这个逻辑时在两个子状态判定之间反复横跳的场景。
第二种是强行拆散耦合,直接不要这两个较大的子状态,把他们拆散成一个个小的boolean判定。
但这就没有封装了,可读性就不要想了,面对一堆boolean维护起来也是极其困难。
Modo被羊的if-else折磨得死来活去时,突然发现了有限状态机FSM这个好东西
有限状态机相当于把羊赋予一个’状态‘,状态可以有有限个种类
但是一只羊同时有且仅有一个状态
每个状态之间有精确的进入和退出条件
我们把羊的状态分为如下三个种类:陌生,逃跑,友好

我们现在先根据之前我们想要通过if-else达到的目的效果来想想各个状态是什么意思:
陌生:
对玩家存在警觉,但不是逃跑
逃跑:
字面意义,即处于逃跑状态
友好:
对玩家毫无防备
接下来我们根据各个状态间的关系添加进入/退出条件即可
(注意,这些是进入/退出条件,也就是说状态是可以持续的,不改变的)

每个tick中,羊仅处于一种状态中,
只有当状态为逃跑时,羊才会调用逃跑方法
以下为具体代码实现:
1 | public enum State { |
1 | switch (mobState.currentState){ |
我们当然也可以用if-else去做到这件事,
但是比起用if-else系统,状态机的可读性和可维护性会好很多。
实际上上述提到的这个羊的行为逻辑只是较规模小的一点实现,一旦决策系统更加庞大之后,状态机的优势就会更好地体现出来
原因在于:
- 我们每时每刻只处于一个状态中
- 我们只要管好局部每个状态时的进入和退出条件即可,可以忽略其他状态之间的交互细节,极大程度上降低了耦合度
上述状态机的具体实现只是其中的一种简单形式, 在Java中即是使用enum配合switch来构成状态机,对于更重型的状态机,有更严谨和标准的方式通过接口来实现(一个真正意义上完整的状态机包含:状态,转换,和执行,其中执行可以在特定状态下也可以在转换时)
状态机可以应付复杂的状态判定中,所以被大量运用在游戏ai逻辑处理中, 包括你可以在mc的反编译源码中找到它
推荐一篇极好的状态机讲解 https://youtu.be/-ZP2Xm-mY4E?si=Qg5BXm2E3TQxlltM