回顾前文,我们解决了添加新的状态需要改变原有代码的问题,使得我们在添加新状态是只需在客户端(即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
每个父类状态机内部有若干个子状态机(可自由添加更多子类状态机)
二、直接上代码
/****************************************************************************
作者:小鱼儿飞丫飞
日期:2020-6-19
文件名:FSM层次状态机头文件
*************************************************************
今天心情不错,突然想明白了困扰自己几个月的HSM(层次状态机)问题,“顿悟”的感觉真是舒畅。这也再次证明不够聪明的人(我),应该勤能补拙。废话少说,把自己的体会总结如下。
HSM被称为层次状态机,用一个专业术语就是具有行为继承,类似OOP中的类继承。那怎么去理解这个行为继承呢,稍后再说。
首先来说说FSM,状态机?如果我们把每个状态都认为是一个函数,并且函数返回
有限状态机(FSM)是表示有限个状态及在这些状态之间的转移和动作等行为的数学模型,在计算机领域有着广泛的应用。通常FSM包含几个要素:状态的管理、状态的监控、状态的触发、状态触发后引发的动作。本文主要阐述一下状态机的几种设计方法。1:switchcase/ifelse设计方法这种设计方法最简单,通过一大堆判断来处理,适合小规模的状态切换流程,但如果规模扩大难以扩展和维护。2:基于表结构的状态机设计方法:建立相应的状态表和动作查询表,根据状态表、事件、动作表定位相应的动作处理函数,执行完成后再进行状态的切换。一个通用的状态机处理模块的设计如下:假设我们的状态图如下:相应的状态机设置如下:in
上一篇博客提到,对于状态太多的情况下,一般的有限状态机会变得很臃肿,这时候就可以使用层次状态机了。在网上看到了一篇对分层状态机讲解的简单易懂的,现摘抄如下:
原版:http://www.cnblogs.com/zhanlang96/p/4793511.html
如果我们让NPC巡逻两个地方,比如安全的室内,和门口
如 果我们想在一个状态上附加一些状况,例如当NPC在巡逻时,让他接一个电话...
有限状态机是指输出取决于过去输入部分和当前输入部分的时序逻辑电路。有限状态机又可以认为是组合逻辑和寄存器逻辑的一种组合。状态机特别适合描述那些发生有先后顺序或者有逻辑规律的事情,其实这就是状态机的本质。状态机就是对具有逻辑顺序或时序规律的事件进行描述的一种方法。在实际的应用中根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy)型状态机。图1 Mealy型状态转移图状态机的描述方法多种多样,将整个状态机写到1个always模块里,在该模块中既描述状态转移,又描述状态的输入和输出,这种写法一般被称为一段式FSM描述方法;还有一种写法是使用两个
就单片机而言,程序可以分为两类:带操作系统的程序和前后台程序;前后台程序从架构上又分为顺序机和状态机。
广义地说, 任何一个程序都是一个状态机, 因为它总是要记住一些状态, 然后根据输入进行输出。 狭义上说,状态机不是指随随便便的一个程序, 而是指某一类程序, 也就是状态机编程程序。
简单讲就是将行为分为一个一个的状态,状态与状态之间的过渡通过事件的触发来形成。比...
有限状态机(Finite State Machine,简称FSM)是一种描述系统行为的数学模型,可以用来描述离散事件系统的状态演变。下面是关于FSM设计的一些建议:
1. 定义清晰的状态:在设计FSM时,需要明确各个状态的含义和转换条件,确保状态的定义清晰且不重叠。每个状态应该唯一表示一个系统或对象的具体情况,以便于理解和再现。
2. 考虑全面的事件:FSM的转换依赖于事件的发生,因此需要考虑到系统可能遇到的所有可能事件。对系统可能的输入进行分析,以确保设计的FSM可以适应各种情况的变化。
3. 确定状态转换条件:每个状态之间的转换需要定义明确的条件,这些条件通常与特定的输入事件相关。在设计过程中,需要详细考虑这些条件,以确保状态之间的转换符合系统的要求。
4. 简化FSM的设计:尽可能地简化FSM的设计,避免过度复杂化。可以通过减少不必要的状态和转换来简化FSM,提高系统的可读性和可维护性。
5. 考虑FSM的扩展性:在设计FSM时,应该考虑到系统可能的扩展需求。确保FSM的设计具有良好的扩展性和灵活性,以便未来可以方便地进行修改和扩展。
6. 进行充分的测试:设计完FSM后,进行充分的测试以验证其正确性。通过提供各种输入和事件的组合,确保FSM能够按照设计预期的方式行为,并正确处理各种情况。
以上是关于FSM设计的一些建议,通过遵循这些建议,可以设计出高效、可靠的FSM,有效地描述系统的行为和状态变化。