分层状态机

这里说一个分层状态机可能会被混淆的点。

分层状态机(HSM)是有限状态机(FSM)的扩展,因为它将状态分了层次,并进行了对应的“分类”,也就是一个大状态里面可能包含着多个小状态,这些大状态是小状态层层继承下来的子类,我们在最终跳转的时候可能更多只会关注外面“大状态的变化”,而不用去考虑具体的子状态的变化,因为内部具体的“小状态”会在其内部处理其自己的跳转逻辑。

重点在于,分层状态机解决的是传统有限状态机当状态繁多时候的状态功能和跳转维护问题,也就是我们所说的“状态爆炸”问题。它是将各个状态通过树形的层次组织起来,并通过分类来进行抽离,将同类型的各个“小状态”组合起来看做一个“大状态”,从而解决问题
但相比有限状态机,分层状态机通过层级组织优化了状态复用和逻辑结构,其特性并没有发生改变,一个状态机同时只能在一个状态这一点并没有发生变化,千万不要混淆这一点。

并发状态机

并发状态机时由多个独立但协同工作的状态机组成,共同配合实现功能的一种状态机。
对于每个状态机我们是按照需求的关注点去进行分离的,比如人物的基本移动和人物持武器这两个部分的功能就可以进行分离来做。且注意各个状态机都独立维护其自己的逻辑,两者的逻辑都相对独立。
但这里可能会引申出一个疑问,如果状态机之间需要交互怎么办?
确实人物控制和持武器的功能是极有可能存在需要交互的地方的,这种情况我们可以通过粗糙的if检测来做,也可以通过万能的事件分发,也就是状态机内部的事件通信机制来进行实现。
之所以说是状态机内部的事件通信是因为这里其实有必要可以实现一下,毕竟当事件系统总线里面的消息数量过多的话是会影响性能的。

可共存的多状态和状态切换的判断

的确很多地方都在说状态内功能执行的重要性,但是状态切换判断同样也是一个不可忽视的重点。
上面我们所说的有一个重点便是:一个状态机一次只能存在一个状态。
这里我们可能需要暂时放一下这个点,因为实际的项目开发中是可能存在需要一个角色身上同时存在多个状态的情况的。

这种情况需要换个角度思考一下问题,比如一个聊天的状态和一个坐下的状态。如果我们出现了希望来聊天的状态能够坐下的情况,按照一般的思维我们可能希望存在一个“聊天坐下”的状态,也就是在这三个状态之间进行切换,并实现每个状态的功能。

但这里如果我们换一下视角,还是两个状态————聊天和坐下。但是我们让这两个状态能够共存,也就是说一个角色同时处于两个状态,并根据这种情况去执行功能。

说到这里你可能会觉得奇怪,这和上面说到的分层和并发本质并不是一回事,把它成为“状态叠加设计”的说法可能更合适一点,而这种设计可以应用到分层和并发等具体的状态机形式里面去。

这种形势下的状态之间的切换往往存在三种情况:

  1. 能够共存存在且互不冲突
  2. 会覆盖之前的状态
  3. 互斥,不能切换到对应的状态

那么按照这种形式,我们可以如何去管理一个角色身上的状态,并去执行对应的功能呢?
存在一些使用多状态判断的项目,其逻辑可能并不会直接写在状态里面,而是在别处实现。而状态判断的意义在于功能执行的限制和保障。
这可能就会变成一个单纯的“状态冲突”判断的组件。
当然,这并不意味着就不能以状态机为核心去书写对应的逻辑。把逻辑写在组件和动作事件里面去固然不错,但把对应状态的逻辑内聚在对应状态里面同样是一种不错的形式。这个就要根据项目实情仁者见仁智者见智了。

以及在协同开发中,也可能存在前端本地需要本地进行状态切换,而不需要同步给其他人的情况,这种情况我们就可以专门维护一个前端本地的状态列表去进行实现,但是在更新状态的时候需要及时把对应的冲突状态给去掉。

可能出现的问题

不当操作和过度设计

  • 有限状态机的“状态爆炸”。这个点在上面已经反复说明过了,在考虑到简单状态无法实现的时候一定要开始从设计层面上进行修改了,比如考虑层次状态机,而不是硬堆状态。
  • 层次状态机的过度分层。我们一般建议的是层级分个3层就差不多了,4层已经是顶天了,再往上就会导致状态关系难以追踪,其带来的不仅是调试上的困难,还有后面功能维护的困难。
  • 并发状态机的过度交互。多个状态机中的交互在一些情况下是必要的,在实现这一步的时候就要考虑到绝对不能让状态之间的逻辑给耦合高了。用内部事件是一种方式,如果不用的话,一定要注意这一点。

归根结底,其实在做的时候我们秉持最基本的设计开发原则就能减少很多错误了,上面这些其实也就是基本原则的体现,比如单一职责,事件驱动等。

状态间共享数据需要额外设计

  • “黑板模式”。也就是GF里面的数据缓存和获取的形式进行传递,这就是我们所说的“黑板模式”。
  • 数据传递,切换的时候传递进去就行。
  • 事件通信。万能的事件可以解决一切问题。
  • 以状态机做中介传递。这种不是很推荐,把数据冗余在状态机里面的话,有可能会造成混乱。

参考文档