【前言】
回顾前文,我们解决了添加新的状态需要改变原有代码的问题,使得我们在添加新状态是只需在客户端(即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