问题背景
假设有一个玩家类Player,功能是根据不同输入切换不同的状态。例如按下J攻击状态,K跳跃状态,A/D移动状态等。
解决方案或许很简单,用多个if else嵌套,然后实现对应逻辑即可。
if(按下J){
切换攻击状态
}else if(按下K){
跳跃状态
}....
然而有个显而易见的bug,如果在攻击下按J,会同时跳跃。总结来说,它犯了两个错误:复杂分支和可变状态。
有限状态机
尝试着画出上方逻辑的流程图。
由图可知,流程图覆盖的核心部分:状态,输入,和转移
展开来说,有限状态机的要点是:
- 拥有状态机所有可能状态的集合。
- 状态机同时只能存在一个状态。
- 一连串的输入或事件被发送给状态机。
- 每个状态都有一系列的转移,每个转移与输入和另一状态有关。
接下来,试着实现这个有限状态机。
为了方便定义状态,建立枚举表示状态种类。
enum State{
STATE_STANDING,
STATE_JUMPING,
....
}
当对状态进行处理时,只需要一个switch语句。
void Handle(输入){
switch(当前状态){
case STATE_STANDING:
if(输入J){
做出攻击状态行为
}
break;
case STATE_JUMPING:
if(输入K){
输入跳跃状态行为
}
break;
...
}
}
然而,如果我们想新加一个动作:蹲下状态下可以进行蓄力。
想要在有限状态机上进行功能增加,至少需要一个添加一个chargeTime的字段,还需要一个计时器,实现很困难。
状态模式
在同一个类中编写不同状态的不同功能是十分混乱的,所以可以把不同的状态定义成不同的类(State)。这就是基本的状态模式。
这样,就能在下蹲模式里单独添加chargeTime字段和Timer方法。
状态模式的定义:“当一个对象内在状态改变时允许改变其行为,这个对象看起来像是改变了类”。
- Context——状态拥有者
- 让外界得知状态的改变或者通过外界改变内部状态。
- 有状态属性的类。
如上述问题中的Player类。
- State——状态接口类
规范具体的ConcreteState在特定下表现的行为。
如在上述问题中,有一个计时器接口,用来实现如何时能结束当前行为等父功能和蓄力等子功能。 - ConcreteState——具体状态类
- 继承自State
- 实现Context在相应状态下该有的行为。
如上述问题中的站立、下蹲、攻击等状态。
状态的切换,一般来说有两种方法:
- 交由Context本身,按条件在各状态间切换
- 产生Context类对象时(构造函数),马上指定初始状态给Context,而在后续执行过程中状态转换则由State对象负责,Context对象不再介入。
根据有限状态机的定义,推荐使用第二种。
回到上面的流程图,状态对象本身比较清楚”在什么状态下,可以让Context对象转换到另一个State状态“。所以在每个ConcreteState类的程序代码中,可以看到”状态转换条件“的判断。以及设置哪一个ConcreteState对象成为新的状态。
因此判断条件及状态属性都被转换到ConcreteState类中。故而,可以缩减Context类的大小。
并发状态机
假定我们需要给上述问题Player类新加一个Gun类。当她拿着枪的时候,她还是能做她之前的任何事情:跑动,跳跃,跳斩,等等。但是她在做这些的同时也要能开火。
如果Player有n个状态,Gun有m个状态,尝试用有限状态机解决,一共有**nm个状态。
但如果把Player和Gun看成两个单独的状态机,那么就只有n+m个状态。
换句话说,同时拥有两个Context
分层状态机
当每个状态都有重复功能代码的时候,可以使用分层的方法。
对于面向对象,可以使用继承的方法。但是这也同时增加了程序的耦合。
下推状态机
多个基础状态绑定重复多次时,利用状态栈,把多个独立的状态捆绑到一起。
状态模式的应用
- 场景的切换
每个场景(ConcreteState)抽象为一个状态xxxState,场景基类(State)定义场景开始、结束、更新等接口ISceneState,场景控制类(Context)保持当前游戏状态,并作为其他功能模块类的互动接口,也是执行场景转换的地方SceneStateController。 - 角色AI
使用状态模式来控制角色在不同在状态下的AI行为。 - 游戏服务器连线状态
- 关卡进行状态
板子
首先画出有限状态机
Context.cs
public class Context
{
State m_state = null;
public void Request(int value)
{
m_state.Handle(value);
}
public void SetState(State theState)
{
m_state = theState;
Console.WriteLine("Context.SetState: " + theState);
}
}
State.cs
public abstract class State
{
protected Context m_context;
public State(Context theContext)
{
m_context = theContext;
}
virtual public void Handle(int value)
{
Console.WriteLine("Value: " + value);
}
}
ConcreteStateA
public class ConcreteStateA:State
{
public ConcreteStateA(Context theContext):base(theContext){ }
public override void Handle(int value)
{
base.Handle(value);
Console.WriteLine("ConcreteStateA");
if (value >= 10)
m_context.SetState(new ConcreteStateB(m_context));
}
}
ConcreteStateB
public class ConcreteStateB:State
{
public ConcreteStateB(Context theContext):base(theContext){ }
public override void Handle(int value)
{
base.Handle(value);
Console.WriteLine("ConcreteStateB");
if (value < 10)
m_context.SetState(new ConcreteStateA(m_context));
else if (value >= 20)
m_context.SetState(new ConcreteStateC(m_context));
}
}
ConcreteStateC
public class ConcreteStateC:State
{
public ConcreteStateC(Context theContext):base(theContext){ }
public override void Handle(int value)
{
base.Handle(value);
Console.WriteLine("ConcreteStateC");
if(value < 20)
m_context.SetState(new ConcreteStateA(m_context));
}
}
客户端代码就不写了。