简述游戏开发中的状态机

为什么我们需要状态机

实行较多状态的角色,把动作全写在一个部分中会导致维护成本高,拓展性低
例如:走路,跳跃,射击,躲避的相互转换,有些可以转换,有些不能,实现逻辑复杂
(满屏幕都是if - else)

状态模式switch实现

//包含着所有的状态
enum class State{StateA, StateB, StateC, ...} activeState;
...
//通过switch语句切换状态,根据具体情况实现细节
switch (activeState)
{
    case State.StateA:
        ...
        break;
    case State.StateB:
        ...
        break;
    .......
}
...

状态机的原形,用一个枚举表示当前的状态,通过填充完善switch语句实现状态之间的切换,但是依然有维护成本高拓展低的缺点(虽然确实是比用if - else堆好)

Finite State Machine(FSN)有限状态机

最基本的状态机,一般来说其他状态机都是这种状态机的变体
对于状态机的理解,最好就是画个图(如图结构,方框是状态,箭头是状态之间的联系)

简述游戏开发中的状态机

有限状态机强调的是状态之间的切换,以及对不同状态的封装,所以实现方法一般可以根据需求调整
以下参考了《游戏人工智能》的实现
首先需要个基类State和基类Translation

//状态基类,所有状态都继承这个类
class State
{
public:
    virtual ~State(){}
    virtual OnStateEnter(){}    //进入此状态执行一次
    virtual OnUpdate(){}        //每一帧执行一次
    virtual OnStateExit(){}     //跳出状态时调用一次
    list<Translation> translations; //状态迁移列表
};

//状态迁移
class Translation
{
public:
    virtual ~Translation(){}
    virtual bool isValid() = 0;         //用于判定迁移,可切换返回true
    virtual State* getNextState() = 0;  //进入下一个状态
    virtual void onTransition(){}       //迁移时调用
};

State是每一个状态都会继承的基类,translations中存着他指向其他状态的Translation,通过每一帧遍历所有Translation的isValid()来判定是否可以跳转,若可以跳转则执行自身的OnStateExit(),并将当前的状态设置为getNextState()获得的状态

然后还有状态机类,用来管理所有状态:

//状态机类
class FiniteStateMachine
{
public:
    void Update();          //每一帧运行一次
    State* initialState;    //初始状态
    State* activeState;     //正在运行的状态
    
protected:
    list<State> states;     //所有状态的实例
};

void FiniteStateMachine::Update()
{
    //遍历活动状态的所有迁移,若有可用的,则切换状态
    list<Translation>::iterator itr = activeState->translations.begin();
    for (int i = 0; i < activeState->translations.size(); ++i, ++itr)
    {
        if (itr->isValid())
        {
            activeState->OnStateExit();     //退出调用
            activeState = itr->getNextState();  //切换活动状态
            itr->onTransition();                //切换调用
            activeState->OnStateEnter();    //进入调用
            return;                 //直接返回
        }
    }
    //如果没有状态切换,运行一次update
    activeState->OnUpdate();
}

接下来具体的状态细节就要具体继承,具体实现

Hierarchical Finite State Machine (HFSM) 分层状态机

分层状态机相当于对有限状态机的进一步封装,将复数个状态封装成一个大的状态,再用一个“历史状态”记录切出此状态时运行的子状态,就可以在大状态间切换(结构如图)

简述游戏开发中的状态机

图中将StateA和StateB加入到一个更高层的状态StateD中,只需退出时记录状态,就可以在CD状态间切换,增加了状态C的复用性(省略了AC,BC间的联系)
主要思想一是包装更高的层次,二是高层的切换放到更高层来觉得以提高复用
分层状态机的实现主要分两种:
一、父状态就是一个状态机,可以往里面添加状态
二、添加一个状态栈储存父子状态,每一个状态都是栈下一个状态的子状态,进入状态时入栈,结束状态时出栈并发送一个消息,新栈顶状态不能处理就再出栈,直到栈顶状态能处理这个消息为止。

并发状态机

并发状态机可以理解为一个对象拥有两个同时运作的状态机,两个状态机相互独立(但可以通过修改对象的状态进行通信),例如我们可以将腿部动作(站立,下蹲,奔跑)和手部动作(持枪,空手,瞄准)分离。

下推状态机

下推状态机的核心是他有一个状态栈(但和层次状态机实现的栈完全不同,层次状态机的栈是为了记录父状态,这个栈是为了纪录上一个状态)。
状态栈主要解决的是状态机无法得到上一轮执行的状态。
实现时,在状态机中加一个栈,在进入状态时将状态入栈,退出状态时将状态出栈,运行时则运行栈顶的状态。
例如,我在做作业的时候饿了,想去吃东西。饿了去吃东西是多数状态都能进入的状态,并且结束“吃东西”的状态后我要回复原来的状态。

在运行时,状态机已经将“做作业“入栈
然后“我饿了”,将“吃东西”入栈
“吃东西”结束,将“吃东西”出栈
继续运行栈顶的“做作业”(事实上不可能)

若有错误,欢迎指出