# 游戏中的状态模式

“允许一个对象在其内部状态改变时改变自身的行为，对象看起来好像是在修改自身类。”

介绍状态模式，不能抛开游戏里面的有限状态机（finite state machines FSM），还有层次状态机（hierarchical state machine）和下推
自动机（pushdown automata）。

假设在开发主角，玩家输入来控制主角的行为，当按下B键时，应该跳跃

```cpp
void Heroine::handleInput(Input input)
{
　if (input == PRESS_B)
　{
　　yVelocity_ = JUMP_VELOCITY;
　　setGraphics(IMAGE_JUMP);
　}
}
```

很明显这样不对，应该还有如果主角着地将 isJumping_设置回false的代码。

没有阻止主角“在空中跳跃”，当主角跳起来后继续按下B键，添加一个isJumping_布尔值变量来追踪主角的跳跃

```cpp
void Heroine::handleInput(Input input)
{
    if(input == PRESS_B)
    {
        if(!isJumping_)
        {
            isJumping_ = true;
            // Jump...
        }
    }
}
```

想实现主角的闪避动作，当主角站在地面上的时候，如果玩家按下下方向键，则躲避，如果松开此键，则站立

```cpp
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);
　}
}
```

上面存在问题：

1. 按下方向键来闪避
2. 按B键从闪避的状态直接跳起来
3. 玩家还在空中的时候松开下键

需要添加另外一个布尔标志位来解决

```cpp
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);
　  }
　}
}
```

如果主角可以在跳起来的过程中，按下方向键进行一次俯冲攻击就更好了

```cpp
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了，主角在跳跃状态的时候不能再跳，但是在俯冲攻击的时候却可以跳跃，又要添加一个成员变量。

书中的这话我很喜欢：你崇拜的一些程序员，他们总是看起来会编写完美无瑕的代码，然而他们并非超人，相反，他们有一种直觉会意识到哪种类型
的代码容易出错，然后避免编写出这种代码。

## 有限状态机

```cpp
躲避------释放Down----站立---按下B-->跳跃--按下Down-->俯冲
    <-----按下Down---
```

* 你拥有一组状态，并且可以在这组状态之间进行切换。
* 状态机同一时刻只能处于一种状态。
* 状态机会接收一组输入或事件
* 每一个状态有一组转换，每一个转换都关联着一个输入并指向另一个状态。

状态枚举

```cpp
enum State
{
　STATE_STANDING,
　STATE_JUMPING,
　STATE_DUCKING,
　STATE_DIVING
};
```

```cpp
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;
　}
}
```

这样写代码的话就非常清晰。

如果在Heroine类中添加一个chargeTime_成员来记录主角蓄能的时间长短，

```cpp
void Heroine::update()
{
　if (state_ == STATE_DUCKING)
　{
　　chargeTime_++;
　　if (chargeTime_ > MAX_CHARGE)
　　{
　　　superBomb();
　　}
　}
}
```

在主角躲避的时候重置这个蓄能时间

```cpp
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...


　}
}
```

## 引入状态模式

```cpp
class HeroineState
{
public:
　virtual ~HeroineState() {}
　virtual void handleInput(Heroine& heroine, 
　　　　　　　　　　　　　　　Input input) {}
　virtual void update(Heroine& heroine) {}
};
```

为每一个状态定义一个类

```cpp
class DuckingState : public HeroineState
{
public:
　DuckingState()
　: chargeTime_(0)
　{}

　virtual void handleInput(Heroine& heroine, 
　　　　　　　　　　　　　　　Input input) {
　　if (input == RELEASE_DOWN)
　　{
　　　// Change to standing state...


　　　heroine.setGraphics(IMAGE_STAND);
　　}
　}

　virtual void update(Heroine& heroine) {
　　chargeTime_++;
　　if (chargeTime_ > MAX_CHARGE)
　　{
　　　heroine.superBomb();
　　}
　}

private:
　int chargeTime_;
};
```

委托状态，

```cpp
class Heroine
{
public:
　virtual void handleInput(Input input)
　{
　　state_->handleInput(*this, input);
　}

　virtual void update() { state_->update(*this); }

　// Other methods...


private:
　HeroineState* state_;
};
```

状态对象应该放在哪里？

### 静态状态

如果一个状态对象没有任何数据成员，那么它的唯一数据成员便是虚表指针了，那样的话就没必要创建此状态的多个实例了。

```cpp
class HeroineState
{
public:
　static StandingState standing;
　static DuckingState ducking;
　static JumpingState jumping;
　static DivingState diving;
  // Other code ...
};
```

修改状态的方式

```cpp
if(input == PRESS_B)
{
    heroine.state_ = &HeroineState::jumping;
    heroine.setGraphics(IMAGE_JUMP);
}
```

### 实例化状态

有时候上面的静态状态方法可能不行，对于刚才的躲避状态就不行，因为它有一个chargeTime_成员变量，当然你可以搞
骚操作把属性搞到主角对象里去。

```cpp
void Heroine::handleInput(Input input)
{
    // 返回想要切换到的状态
　HeroineState* state = state_−>handleInput(  
　　　　　　*this, input);
　if (state != NULL)
　{
    // 删除老状态
　　delete state_;
    // 切换状态
　　state_ = state;
　}
}
```

例如切换状态

```cpp
HeroineState* StandingState::handleInput(  
　　　　　 Heroine& heroine, Input input)
{
　if (input == PRESS_DOWN)
　{
　　// Other code...


　　return new DuckingState();
　}
　// Stay in this state.


　return NULL;
}
```

为状态添加 enter 和 leave 钩子

```cpp
class StandingState : public HeroineState
{
public:
　virtual void enter(Heroine& heroine)
　{
　　heroine.setGraphics(IMAGE_STAND);
　}
  virtual void leave(Heroine& heroine)
  {
    // ...
  }


　// Other code...

};
```

enter和leave由上层调用

```cpp
void Heroine::handleInput(Input input)
{
　HeroineState* state = state_->handleInput(  
　　　　　　*this, input);
　if (state != NULL)
　{
    // call the leave action
    state_->leave(*this);
　　delete state_;
　　state_ = state;

　　// Call the enter action on the new state.
　　state_->enter(*this);
　}
}
```

## 并发状态机

如果我们决定给主角添加持枪功能，当她持枪的时候，仍然可以：跑、跳和躲避等。但是，她也需要能够在这些状态过程中开火。

我们不要把两种不同的状态硬塞到一个状态机里面去，比较直观的解决方法就是，分开成两个状态机。

主角定义n种状态和m种能携带的武器状态，如果使用一个状态机表示则需要nxm个状态，如果需要两个状态机，
那么状态组合仅是n+m。

```cpp
class Heroine
{
　// Other code...

private:
　HeroineState* state_;
　HeroineState* equipment_;
};
```

当主角派发输入事件给状态类时，需要给两种状态机都派发

```cpp
void Heroine::handleInput(Input input)
{
　state_->handleInput(*this, input);
　equipment_->handleInput(*this, input);
}
```

## 层次状态机

把主角的行为更具象化之后，她可能会包含大量相似的状态，比如，她可能有站立，走路、跑步和滑动状态。
在这些状态种任何一个状态时按下B键，我们的主角要跳跃；按下方向键，主角要躲避。

把共同的逻辑提炼到基类状态中

```cpp
class OnGroundState : public HeroineState
{
public:
　virtual void handleInput(Heroine& heroine,   
　　　　　　　　　　　　　　　Input input)
　{
　　if (input == PRESS_B)　// Jump...


　　else if (input == PRESS_DOWN)　// Duck...


　　}
　}
};
```

子状态继承它

```cpp
class DuckingState : public OnGroundState
{
public:
　virtual void handleInput(Heroine& heroine,   
　　　　　　　　　　　　　　　Input input)
　{
　　if (input == RELEASE_DOWN)
　　{
　　　// Stand up...


　　}
　　else
　　{
      // 一层一层向上找
　　　// Didn't handle input, so walk up hierarchy.
　　　OnGroundState::handleInput(heroine, input);
　　}
　}
};
```

## 下推状态机

它要解决的是有限状态机没有历史记录的问题，我们知道当前状态，但是，我们并不知道之前的状态是什么。

比如有一个开枪状态，一种新的状态来播放开枪的动画，发射子弹并显示一些特效。
但开枪后要回到什么状态呢。

本来，有限状态机有一个指向当前状态的指针，而下推自动机有一个状态栈。
在一个有限状态机里面，当有一个状态切进来时，则替换掉之前的状态。下推自动机可以让你这样做，同时它还提供其他选择：

* 你可以把这个新的状态放入栈里面。当前的状态永远存在栈顶，所以你总能转换到当前状态。但是当前状态会将前一个状态压在栈中自身的下面而不是抛弃掉它。
* 你可以弹出栈顶的状态，该状态将被抛弃。与此同时，上一个状态就变成了新的栈顶状态了。

```cpp
|站立
|站立 开火 <--push
|站立 -->pop
```

状态机得使用范围是有限的，在游戏AI领域，趋势越来越倾向于行为树和规划系统。
