【前言】

回顾前文,我们解决了添加新的状态需要改变原有代码的问题,使得我们在添加新状态是只需在客户端(即player类)中增加代码,而不需要更改状态机的任何代码。

以攻击为例,跳跃攻击、行走攻击是不一样的,最开始时我们需要判断上一个状态是什么来角色在攻击状态怎样攻击;随后,我们需要创建跳跃攻击状态和行走攻击状态,然后创建转移类。

但是,如果行走或跳跃时持有武器,那么攻击方式与没有持有武器时不一样,我们需要创建新的状态类和转移类。

这些新创建的状态都属于攻击这个大状态下的具体攻击状态,每个具体的攻击状态可能都需要执行一些相同的操作,并使得整个状态机更加庞杂。

这时,我们需要将这些近似的状态归属为一个更大的状态下,这就要用到层次状态机。

【概念】

这个状态机共有s0,s1,s11,s2,s21,s211六个状态,除了s0,其他的都是嵌套的状态(nested state)。s0是s1的超状态(superstate),也可以说是父状态(parent state),s1是s0的子状态(substate)。其他的以此类推,就像父类和子类一般,很好理解。

其有以下几个特点:

  • 所有输入和事件先在超状态中处理,超状态中没有处理的信息在子状态中处理
  • 子状态只需要处理与超状态不同的东西,可以共用超状态的行为,也即超状态的行为可以被子状态继承,即行为继承(behavior inheritance)
  • 在进入的时候,先进入超状态,再进入子状态;在退出的时候,先退出子状态,再退出超状态,类似继承时类的构造器和析构器

【实现分析】

对于s1来说,其是一个状态,在状态机的控制下实现到s2的转换,同时,其也是一个状态机,控制子状态s11的转换。因此,状态机类应该继承状态类。

对于状态机而言,其有三种类型的状态转移:作为状态时与同层的状态间的转移,例如s2与s1之间;作为状态时与子状态间的转移,例如s2到s21;作为状态机时,控制子状态间的转移。

前文说过,子状态间的转移集合既可以放在状态机类中,也可以放在状态类中。我们这里放在状态类中。这样在状态机类中我们只需要添加一个新的转移集合List<TransitionBase<TState>> subTransitions,即状态机作为状态时与子状态间的转移。

结合层次状态机的特点,按照轮询转移的方式,我们分析下状态机进入s11到从s11到s211的转移过程。

  • 最外层状态机进行分发进入s0
  • s0执行运行逻辑,随后进行分发,可以分发到s1,进入s1
  • s1执行运行逻辑,随后进行分发,可以分发到s11,进入s11
  • s11执行运行逻辑,但不是状态机,不能进行分发
  • 随后在某一帧中从s11到s211的转移被满足,从最外层状态机进入时还是分发进入s0
  • s0执行运行逻辑,随后进行分发,因为之前已经分发到s1,也就是当前状态CurState为s1,这是要执行子状态间的转换。从s11到s211的转移被满足,可以从s1转到s2。
  • 在从s1转到s2前,先从s11退出,返回到s1(从s11到s1可以被看做是进入了s1,也即没有进入动作),再从s1退出,随后进入s2
  • s2执行运行逻辑,随后进行分发,可以分发到s21,进入s21
  • s21执行运行逻辑,随后进行分发,可以分发到s211,进入s211
  • s211执行运行逻辑

可以看到,随着状态机层次越来越多,深层次的两个状态间的转移会很麻烦,要绕很大一圈,且在逻辑上的一个转移(例如从s11到s211的转移)要在代码上有多个转移类(从s11到s1,从s1到s2,从s2到s21,从s21到s211)。

基于事件触发方式的状态转换的逻辑与上述相同,区别是在接收到事件后直接进行一系列的进入和退出动作,而不是在Update中执行。

【代码实现】

需要更改的主要是状态机类,状态类和转移类与之前相同,此处只放与之前不同的部分的代码,且只是实现核心的状态转换,进入退出动作等。

public class FSM<TState,TEvent>:StateBase<TState>,IFSM<TState>
{private List<TransitionBase<TState>> subTransitions = new List<TransitionBase<TState>>();//状态机作为状态时与子状态间的转移public override void Update()//状态机的执行,即切换状态{timer.runTime += Time.deltaTime;if (delayedStates.Count > 0) //有延迟状态,直接返回return;OnUpdate?.Invoke();//作为状态时的逻辑nextState = Dispatch();//状态机进行分发if (nextState != null)//分发到某个状态{if (curState == null)//从状态机到子状态{RequestChangeState(this,nextState);//从状态机到子状态的转换}else //子状态间的转换{SubStateChange();}curState.Update();}}public override void Enter(){base.Enter();}public override void Exit()//作为状态时,先等子状态退出{if (curState != null){RequestChangeState(curState,this);//从子状态到状态机的切换curState = null;}base.Exit();}public StateBase<TState> Dispatch(){foreach (TransitionBase<TState> item in subTransitions)//轮询{if (!item.fromState.Equals(stateId)){continue;}if (item.CanTransition()){return GetState(item.toState);//返回状态机内的状态}}return null;}public StateBase<TState> GetCurRunState()//获取当前正在运行的某个状态,只会有一个状态在运行{if (parent != null)//只从最顶层状态机往下查找return null;if (curState == null){return this;}//向下查找IFSM<TState> subFSM = curState as IFSM<TState>;if (subFSM == null)//当前子状态不是状态机return curState;return subFSM.GetCurRunState();}private void SubStateChange(){nextState = curState.HandleTransition();//子状态间的转换if (nextState != curState && nextState != null){RequestChangeState(curState, nextState);}}
}public interface IFSM<TState>
{StateBase<TState> GetState(TState state);void RequestExit(StateBase<TState> state);StateBase<TState> Dispatch();StateBase<TState> GetCurRunState();
}

【参考】

lec-HSM.pdf (upenn.edu)https://www.cis.upenn.edu/~lee/06cse480/lec-HSM.pdf

有限状态机FSM详解(5)——层次状态机HSM相关推荐

  1. PLC面向对象编程系列之有限状态机(FSM)详解

    编写PLC控制机器动作类程序时,当分支比较少的时候我们使用IF ELSE语句解决,当分支比较多的时候,我们要使用CASE,END_CASE语句解决,针对分支的复杂程度选择合适的程序解决.当分支过多时, ...

  2. 有限状态机FSM详解(1)——最简单的FSM

    [基本概念] 有限状态机(finite-state machine,FSM)可以解决有限个状态转换的问题.具体的定义和相关的概念先不说,我们直接通过例子来理解. 我们知道一个角色通常会至少有idle, ...

  3. 有限状态机FSM详解(一)

    有限状态机在维基百科中的解释是: 有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模 ...

  4. 有限状态机FSM详解(2)——采用状态模式的FSM

    [状态模式] 状态模式主要解决的是当控制一个对象状态转换的条件判断过于复杂时,把判断逻辑转移到表示不同状态的一系列类中,从而简化复杂的逻辑判断.(如果状态转换条件判断很简单,就没必要用状态模式了) [ ...

  5. 层次状态机-HSM应用解析

    在上一面文章中分析了HSM的核心代码,本文章主要是对HSM的应用解析,以相机拍照为例子,对状态和事件的调用,实现拍照.查看照片.切换拍照模式.低电量关机等功能. 一.状态和事件 这个例子里面用了四种事 ...

  6. 层次状态机-HSM代码解析

    有限状态机在学习和工作中经常能够遇到,前面的文章也有使用到.但是对于层次状态机网上的学习资源却很少,导致一直不理解这个工作机制,后面偶然在GitHub看到一篇文章,深入学习后发现层次状态机太实用了,如 ...

  7. 英飞凌 AURIX 系列单片机的HSM详解(5)——HSM硬件加速模块的使用

    本系列的其它几篇文章: <英飞凌 AURIX 系列单片机的HSM详解(1)--何为HSM> <英飞凌 AURIX 系列单片机的HSM详解(2)--与HSM相关的UCB和寄存器> ...

  8. 有限状态FSM详解(3)——通用的FSM

    [如何省略写转移和状态类] 在之前的FSM实现中,我们发现每添加一个新的状态时,需要写一个新的状态类,多个转移类,同时,还有可能需要修改已有的转移类的CanTransition方法,如何避免这些呢? ...

  9. yaahp使用教程_[层次分析法(详解)] yaahp层次分析法教程

    AHP (Analytic Hierarchy Process)层次分析法是美国运筹学家T. L. Saaty教授于二十世纪70年代提出的一种实用的多方案或多目标的决策方法,是一种定性与定量相结合的决 ...

最新文章

  1. C语言 条件编译详解
  2. There is no isNullOrEmpty for collections in Guawa
  3. Eclipse + Spring boot +mybatis + mysql
  4. Tensorflow 10分钟快速上手
  5. java解析nes_Java 读写 excel 实战完全解析
  6. c++primer plus 第13章 编程题第2题
  7. Spring的自动装配方法
  8. 这些超实用的电脑快捷键,你都get到了吗?
  9. 人脸颜值评分软件_在线算个颜值,特科学的那种 | 知多少
  10. 698A. Vacations
  11. linux系统怎样写单片机程序,单片机知识是Linux驱动开发的基础之一以及如何学单片机...
  12. BLE - LINK LAYER SPECIFICATION
  13. csdn博客图片复制不过来怎么办?如何转载?
  14. Java开发十二大框架
  15. vb.net 同时给多个属性赋值_传奇技能,第十四祭:装备属性修改与增加新装备...
  16. 浅谈SQL注入防御手段
  17. js定义对象时属性名是否加引号问题
  18. java 微博阅读量怎么算,新浪微博阅读量怎么算
  19. keil5下载仿真出现Flash download failed-Cortex-M3的原因与方案
  20. MATLAB绘画双纵坐标图改纵坐标颜色都为黑色

热门文章

  1. android 仿钢笔代码,仿ps画布钢笔抠图工具代码
  2. 嵌入式LINUX系统程序开发
  3. Windows批处理(cmd/bat)常用命令教程
  4. android 无法添加帐户,安卓手机outlook无法登录、添加帐户
  5. 如何快速高效出高质量效果图
  6. Django cms 教程六:集成博客/新闻模块
  7. 云计算 第七章 云安全(3)概述 云计算面临的安全问题 云安全问题的深层原因 云安全关键技术 云计算信息安全的国内外标准化
  8. CreateWindow 详解
  9. 利用计算机引号作用,引号有什么作用
  10. 互联网财富管理平台应该怎么做?(上篇)