游戏设计模式--状态机模式(翻译整理)(上篇)

状态机设计模式本身有比较多的基础理论在里面,但在这里,我们游戏所用的有限状态机FSMs,我们用一种简单的方式,来学习和分析。当我们知道了,为什么要用,有什么优点后,对状态机的理解就容易多了。当然在下面的例子中,我们的代码并不是完美的,有很多细节是需要自己去完善的。原贴:

http://gameprogrammingpatterns.com/state.html

现在就让我们开始吧:)

假象一下,我们在开发一个横版卷轴类游戏。我们的工作时实现一个英雄角色在游戏世界里面运动。也就是说,我们的角色需要响应我们的玩家的输入。如按下B键,角色会跳起来,代码如下:

void Heroine::handleInput(Input input){

  if (input == PRESS_B)

  {

    yVelocity_ = JUMP_VELOCITY;

    setGraphics(IMAGE_JUMP);

  }}

发现BUG没有?

上面的代码没有禁止掉空中再次跳跃的情况,如果玩家一直按B,那么角色就会一直往天上飞,不落下来了。最简单的修正这个BUG的方式就是,添加一个IsJumping_boolean变量。当玩家跳跃的时候做检查:

 

void Heroine::handleInput(Input input){

  if (input == PRESS_B)

  {

    if (!isJumping_)

    {

      isJumping_ = true;

      // Jump...

    }

  }}

 

下一步,当角色在地面上时,玩家按下 Down 键时,让角色蹲下,当玩家释放开Down键时,角色站立起来:

void Heroine::handleInput(Input input){

  if (input == PRESS_B)

  {

    // Jump if not jumping...

  }

  else if (input == PRESS_DOWN)

  {

    if (!isJumping_)

    {

      setGraphics(IMAGE_DUCK);

    }

  }

  else if (input == RELEASE_DOWN)

  {

    setGraphics(IMAGE_STAND);

  }}

发现BUG没有?

通过上面实现的代码,玩家可以做以下的操作:

按下Down键,角色蹲下。

1.按下键,角色蹲着的地方飞了起来。

3角色在空中,释放开Down键后。

这几步操作完后,就会发现,玩家在跳跃的空中,恢复到了站立的动画。

为了解决这个BUG,我们只有再加一个标志。。。

void Heroine::handleInput(Input input){

  if (input == PRESS_B)

  {

    if (!isJumping_ && !isDucking_)

    {

      // Jump...

    }

  }

  else if (input == PRESS_DOWN)

  {

    if (!isJumping_)

    {

      isDucking_ = true;

      setGraphics(IMAGE_DUCK);

    }

  }

  else if (input == RELEASE_DOWN)

  {

    if (isDucking_)

    {

      isDucking_ = false;

      setGraphics(IMAGE_STAND);

    }

  }}

下一步,如果能让玩家在跳跃到空中的时候,按down键,来一个俯冲攻击,应该很酷:

void Heroine::handleInput(Input input){

  if (input == PRESS_B)

  {

    if (!isJumping_ && !isDucking_)

    {

      // Jump...

    }

  }

  else if (input == PRESS_DOWN)

  {

    if (!isJumping_)

    {

      isDucking_ = true;

      setGraphics(IMAGE_DUCK);

    }

    else

    {

      isJumping_ = false;

      setGraphics(IMAGE_DIVE);

    }

  }

  else if (input == RELEASE_DOWN)

  {

    if (isDucking_)

    {

      // Stand...

    }

  }}

BUG时间又到了,找到了没?

我们之前保证了玩家在跳跃的时候不能再次跳跃,但是现在玩家在俯冲的时候,又可以跳跃了。我们又只有再加一个条件。。。是不是觉得要疯掉了。

这个时候,我们应该能感觉的很明显了,我们现在的解决方案是有问题。每一次在这个段代码上添加新的需求,就会破坏之前的一些逻辑。按照这种情况持续下去,就算是吧需求做完了,后面应该会有一堆的BUG等着我们去修改。

这个时候 有限状态机 就是我们的救命稻草了。

在经历一些纠结和挫折之后,你把桌面上的所有东西都清理干净,然后只留下一个笔和一张纸,开始画一个流程图。你画上了一个个盒子,这些盒子代表着玩家的各种行为状态:站立中,跳跃中,下蹲中,俯冲中。当按键按下,从一个状态变化到另一个状态时,我们用一个条件加一根线,指向我们将切换到的新的状态。



恭喜你,你已经成功的创建了有限状态机了。其实这个来自于计算机分支科学:图灵机或者自动机理论。在这些理论中,有限状态机是最简单的理论之一。

它的要点如下:

你需要一些固定的状态。在我们的例子里面就是站立,跳跃中,下蹲中,俯冲中。

同一个时刻只能在一个状态里面。我们的例子里面,角色是不能同时跳跃中和站立中的。事实上,避免同时多个状态的情况,是我们使用FSM的一个核心原因之一。

一序列输入或者事件发送给状态机。在我们的例子里面,就是按键的按下或者释放事件。

态机中的状态之间可以互相转换,而转换的条件就是上面说的输入或者事件。当我们输入事件来了后,如果当前的状态能够接受该输入,那么就会按照当前的输入,转换到对应的状态中去。

 

例如,在图中,角色在Standing状态时,收到按下 down 键事件,那么就会转换到Ducking状态。当玩家在Jumping状态时,按下down键事件,那么就会转换到Diving状态。

如果在当前状态收到一个没有定义的输入事件,那么忽略该事件。

 

简单的实现方式,用EnumsSwithces

其中一个问题是我们的Heroine类中的boolean变量们,像isJumping_isDucking_他们应该是不能同时为true的。当我们有一堆变量都是boolean,但是同一时刻只能一个为True的话,就说明我们需要用Enums了。

enum State

{

  STATE_STANDING,

  STATE_JUMPING,

  STATE_DUCKING,

  STATE_DIVING

};

用枚举State一个就代替了我们的一堆的是否标识的布尔变量。在前面的代码中,我们是用按照输入来作为条件判断,然后再切的状态。这样能把一个输入下,需要做的事情放在了一起,但是却让多个状态的代码混淆在了一起。现在我们想要一个状态的代码整合在一起,那么我就需要先用状态来做switch

void Heroine::handleInput(Input input){

  switch (state_)

  {

    case STATE_STANDING://这里将一个状态的代码放在了一起

      if (input == PRESS_B)

      {

        state_ = STATE_JUMPING;

        yVelocity_ = JUMP_VELOCITY;

        setGraphics(IMAGE_JUMP);

      }

      else if (input == PRESS_DOWN)

      {

        state_ = STATE_DUCKING;

        setGraphics(IMAGE_DUCK);

      }

      break;

 

    case STATE_JUMPING:

      if (input == PRESS_DOWN)

      {

        state_ = STATE_DIVING;

        setGraphics(IMAGE_DIVE);

      }

      break;

 

    case STATE_DUCKING:

      if (input == RELEASE_DOWN)

      {

        state_ = STATE_STANDING;

        setGraphics(IMAGE_STAND);

      }

      break;

  }}

这个改变看起来好像没有太大的用,但事实上却是有很大进步。虽然代码中,还是存在不少的条件分支,但是我们将不容易理解的状态这个相应的代码整合在了一起。大家可以对比下前面的代码和这段代码,新的代码在理解上清晰明了了很多。这个就是最简单的实现FSM的方法。

我们的游戏可能会再进一步的扩展,如,我们想要角色在蹲下的时候自动聚气,一定时间后能够释放一个特殊的攻击技能。那么在角色蹲下的时候,我们需要记录一个聚气的时间。

我们在HeroIne中添加一个chargeTime_的变量,用来记录这个时间。同时,我们假象我们已经有了一个每帧可以更新的update()函数。那么我们就可以如下实现:

void Heroine::update(){

  if (state_ == STATE_DUCKING)

  {

    chargeTime_++;

    if (chargeTime_ > MAX_CHARGE)

    {

      superBomb();

    }

  }}

同时我们需要在角色开始蹲下的时候,重置这个时间,修改handleInput()

void Heroine::handleInput(Input input){

  switch (state_)

  {

    case STATE_STANDING:

      if (input == PRESS_DOWN)

      {

        state_ = STATE_DUCKING;

        chargeTime_ = 0;

        setGraphics(IMAGE_DUCK);

      }

      // Handle other inputs...

      break;

 

      // Other states...

  }}

好,我们的需求修改完毕。我们只修改了2个函数,同时添加了一个chargeTIme_变量。我们的代码其实已经比较清晰了,但是我们这里添加的变量chargetTIme其实只是对蹲下状态有效的。那么下篇文章中看看我们如果处理,能有更好的整合效果把:)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值