游戏设计模式读书笔记:状态模式

Posted by 蔡华的博客 on April 21, 2018

解决了什么问题

  • 在游戏开发中状态是一种常见的用于描述游戏对象的方法,在状态比较简单的情况下可能通过多个标志位变量就可以组合出不同的状态。例如书中举得英雄行走、跳跃、蹲下等操作。但是一旦当对象的状态可是变得比较多,而且需要通过比较多的变量才能描述清楚时,只使用标志位变量的弊端就出现了:那就是过多的变量在代码编写时需要的条件语句会很多,而且变量的组合也不利于管理。

使用switch管理状态

  • 首先需要将状态从变量组合变为一个明确的字段,此时我们可以使用enum来实现这个。
  • 在switch中对于每个枚举状态开设分支。这样从一定程度上解决了大量变量带来的编程的复杂度。而且从逻辑上清晰的划分了状态。
  • 但是这个仍旧存在问题,用书中的例子来说,当你需要在某个状态中进行一个计时操作时。就需要在switch代码块和update代码块中修改功能,这说明功能没有能很好的封装,你需要修改两处才能实现功能的修改。如果我们在修改功能的时候只修改一个地方就能够避免一些麻烦,比如一次修改就要同时修改多处,万一遗漏了一个就可能造成bug。

状态模式

允许一个对象在其内部状态发生变化时改变自己的行为,该对象看起来好像修改了它的类型。

  • 这句话有点让人难以理解,实际上状态模式是这么干的:
    • 定义状态接口
    • 让每个状态成为一个类并实现接口
    • 对象持有状态对象的引用,从而使得从外部看(实际就是获取对象的某些属性或者调用函数)这个对象具有不同的状态。
  • 对于一个状态的接口一般都包含如下的函数:Enter、Update、Exit。除此之外也可以根据实际情况设计一些函数,比如设计一个Init函数,它是在状态对象实例化时进行初始化用的。
  • 静态状态:如果一个状态它只有函数没有字段,也就是说它是对象无关的,那么完全可以使用静态类来定义状态,这样做可以节省内存,管理起来也比较简单。

有限状态机(FSM)

  • FSM我个人认为其作用在于专职的管理状态,比如状态的创建、切换、销毁等。
  • 对象持有的不在是一个具体的状态对象,而是一个FSM对象。而状态的切换也需要在FSM中实现,FSM中有一个所有状态的集合,同时保存当前的状态的引用。
  • 在切换状态时先要执行当前状态的Exit函数,然后执行下一个状态的Enter。
  • 在unity中对于不同的FSM一般使用一个专门的管理类来管理所有的FSM。

并发状态机

  • 并发状态机和下面谈到的另外两个其实都是在上面所说的基础上的扩展,用于不同的场景。
  • 顾名思义,并发状态机实际上就是对象同时拥有两种不同的状态。以书中的例子来说,就是英雄角色具有一个状态机,而英雄手中的武器也具有一个状态机。
  • 两个不同的状态机之间可能会有交互,为了完成这个,你也许会在状态的代码中做一些粗糙的if测试其他状态来协同,这不是最优雅的解决方案,但这可以搞定工作。

分层状态机

  • 当不同状态具有某个共同点时使用。

举个例子,我们的英雄也许有站立、行走、奔跑和滑铲状态。在这些状态中,按B跳,按下蹲。如果使用简单的状态机实现,我们在每个状态中的都重复了代码。如果我们能够实现一次,在多个状态间重用就好了。

状态可以有父状态(这让它变为子状态)。当一个事件进来,如果子状态没有处理,它就会交给链上的父状态。 换言之,它像重载的继承方法那样运作。事实上,如果我们使用状态模式实现FSM,我们可以使用继承来实现层次。 定义一个基类作为父状态:

class OnGroundState : public HeroineState
{
public:
  virtual void handleInput(Heroine& heroine, Input input)
  {
    if (input == PRESS_B)
    {
      // 跳跃……
    }
    else if (input == PRESS_DOWN)
    {
      // 俯卧……
    }
  }
};
class DuckingState : public OnGroundState
{
public:
  virtual void handleInput(Heroine& heroine, Input input)
  {
    if (input == RELEASE_DOWN)
    {
      // 站起……
    }
    else
    {
      // 没有处理输入,返回上一层
      OnGroundState::handleInput(heroine, input);
    }
  }
};
  • 除了使用继承外,还可以显式的使用状态栈而不是单一状态来表示当前状态的父状态链。栈顶的状态是当前状态,在他下面是它的直接父状态,然后是那个父状态的父状态,以此类推。 当你需要状态的特定行为,你从栈的顶端开始,然后向下寻找,直到某一个状态处理了它。(如果到底也没找到,就无视它。)

下推自动机

  • 下推状态机解决的问题是有限状态机没有任何历史的概念。你记得正在什么状态中,但是不记得曾在什么状态。 没有简单的办法重回上一状态。
  • 有限状态机有一个指向状态的指针,下推自动机有一栈指针。 在FSM中,新状态代替了之前的那个状态。 下推自动机不仅能完成那个,还能给你两个额外操作:
    • 你可以将新状态压入栈中。“当前的”状态总是在栈顶,所以你能转到新状态。 但它让之前的状态待在栈中而不是销毁它。
    • 你可以弹出最上面的状态。这个状态会被销毁,它下面的状态成为新状态。

总结

  • 状态模式在游戏开发中使用的最为广泛的就是FSM,它有效的将游戏对象的不同状态进行了封装,从而使得对象的操作更加简单。
  • 对于单个的游戏对象不同的状态中可能会有更多的操作,比如对象在行走状态下需要控制移动的方向、速度、对于碰撞体的处理等。这个就需要在状态机中进行功能编写了。
  • 对于FSM的实现推荐看下GameFramework中的FSM部分。