前言:状态机模式是一个游戏经常使用的经典设计模式,常被用做管理一种物体的各类状态(例如管理人物的行走,站立,跳跃等状态)。html
(Unity里的Animator就是一种典型的状态机,用于控制动画状态之间的切换)设计模式
假如咱们正在开发一款动做游戏,当前的任务是实现根据输入来控制主角的行为——当按下B键时,他应该跳跃。ide
直观的代码:动画
if (input == PRESS_B) { if (!m_isJumping) { m_isJumping = true; Jump();//跳跃的代码 } }
后来咱们须要添加更多行为了,全部行为以下:ui
站立时按下 ↓ 键 =》 蹲下。this
蹲下时按下 ↓ 键 =》 站立。spa
站立时按下 B 键 =》 跳跃。设计
跳跃时按下 ↓ 键 =》触发 俯冲。3d
if (input == PRESS_B) { //若是在站立时且没在跳跃,则跳跃 if (!m_isJumping && m_isStanding) { m_isJumping = true; player.jump();//跳跃的代码 } } else if (input == PRESS_DOWN) { //若是在跳跃时且没在俯冲,则俯冲 if (m_isJumping && !m_isDiving) { m_isDiving player.dive();//俯冲的代码 } //若是没在跳跃 else if (!m_isJumping) { //若是站立时,则蹲下 if (m_isStanding) { m_isStanding = false; player.sneak();//蹲下的代码 } //若是蹲下时,则站立 else { m_isStanding = true; player.stand();//站立的代码 } } }
能够看到一堆if-else语句很是复杂,要是添加更多行为,其逻辑结构更加难以维护,并且主角的代码又得从新编译(耦合性大)code
有限状态:有限数量的状态。
一个可行的办法是将这些 状态&状态切换&状态对应的行为 封装成类,
(以下图)
这时候能够借助状态机这个设计模式来美化这段代码。
上面的场景中,只有4个状态(跳跃/下蹲/站立/俯冲),这就是有限状态。
因而咱们设计出下面4个状态类(加一个状态的接口类):
//状态接口类 class State { public: //处理输入,而后根据输入转换相应的状态 virtual void handleInput(Player& player,const Input& input) = 0; }; //站立状态 class StandState : public State { public: void handleInput(Player& player, const Input& input) override{ if (input == PRESS_B) { player.jump();//角色跳跃的代码 player.setState(JumpState()); } else if (input == PRESS_DOWN) { player.sneak();//角色蹲下的代码 player.setState(SneakState()); } } }; //跳跃状态 class JumpState : public State { public: void handleInput(Player& player, const Input& input) override { if (input == PRESS_DOWN) { player.dive();//角色俯冲的代码 player.setState(DiveState()); } } }; //下蹲状态 class SneakState : public State { public: void handleInput(Player& player, const Input& input) override { if (input == PRESS_DOWN) { player.stand();//角色站立的代码 player.setState(StandState()); } } }; //俯冲状态 class DiveState : public State { public: void handleInput(Player& player, const Input& input) override { } };
第一次进入游戏时,给角色一个初始状态
player.setState(new StandState());
而后每次接受输入,让角色当前的状态对象去处理就能够了。
player.getState().handleInput(player,input);
简单小结:
能够看到利用状态类对象,咱们把负责的条件逻辑封装到各个状态类里,让代码变得优雅,并且还减小了几个变量的使用(m_isJumping等)。
此外因为有限状态对象的属性是固定不变的,这意味着全部角色都能共享同一个状态(当同种状态时),
因此常见的状态对象存储方式是单例存储或者静态存储(每种状态只生成1个对象),避免了上文每次都要生成新状态对象的开销。
实际中,一些游戏的类可能须要多个状态(平行关系),因而能够写出如下代码
class Player{ State* m_bodyState;//身体状态 State* m_equipmentState;//装备状态 //.....其它代码 };
而后即可以用下列方式处理状态了
void Player::handleInput(const Input& input) { m_bodyState->handleInput(*this,input); m_equipmentState->handleInput(*this,input); }
把主角的行为更加具象化之后,可能会包含大量类似的状态,为了重用代码,便衍生层次状态机的概念。
层次状态主要思想是状态类继承,从而产生层次关系的状态。
例如,蹲下状态和站立状态 继承于 在地面状态。
class OnGroundState : public State { void handleInput(Player& player, const Input& input) override { if (input == PRESS_B){}//....跳跃 } }; class StandState : public OnGroundState { void handleInput(Player& player, const Input& input) override { //当松开↓键,才蹲下去 if (input == RELEASE_DOWN) {}//...蹲下去的代码... else {OnGroundState::handleInput(player,input);} } }; class SneakState : public OnGroundState { void handleInput(Player& player, const Input& input) override { //当松开↓键,才站起来 if (input == RELEASE_DOWN) {}//...站起来的代码... else {OnGroundState::handleInput(player, input);} } };
下推状态机,简单来讲,就是用栈结构存储一系列状态对象。
通常来讲,一个角色只须要一个状态对象,为何要用栈结构存储一堆状态对象?
假设有一个射击游戏的角色,他现正在站立状态,执行栈顶状态中。
忽然遇到敌人进行开火,因而入栈一个开火状态,并继续执行新的栈顶状态。
敌人被击中死亡,开火状态结束。为了恢复到开火前的上一个状态,因而去掉栈顶状态。
这样咱们利用栈就完美模拟了一我的开火以后恢复成站立状态的过程。
简单来讲,
下推自动机适用于须要记忆状态的状态机,这在一些游戏AI是经常使用的手法。(不过如今更流行的游戏AI是用行为树实现)
游戏设计模式系列-其余文章: