在游戏中,游戏人物根据玩家的输入以及人物与游戏世界交互,会有许多动作动画效果的转换。简单的比如马里奥,玩家没有输入,没有发生游戏事件(吃到蘑菇、碰到怪物)时,马里奥大叔静止不动。玩家按下移动按钮,马里奥开始走动,这时需要绘制走路的动画效果。按下跳跃键,绘制跳跃的动画效果。而按键和动作并不是一一对应的关系,比如跳跃过程中按下左右移动键,并不能绘制走路的动画效果。
这些状态之间的转换可以使用if else,switch case,语句来完成,如下:
//这里把人物写成一个Role类class Role{public:enum STATE{IDLE,WALK,JUMP,}void JumpInput(){ _state = JUMP; //..设置当前动画为跳跃的动画}void WalkInput(){ if (_state == IDLE) { _state = WALK; //..设置当前动画为行走的动画 }}// 跳跃后角色落地,由碰撞检测触发void OnLand(){ _state = IDLE;}private:STATE _state;}
貌似挺完美,嗯,这样完美的情况仅仅只限于状态比较少,状态转换比较简单的条件下,如果人物状态一增多,状态转换条件一变复杂,那将变成一场噩梦,代码中会产生大量的if else条件判断,比如我们再增加一个蹲下的状态,蹲下时按下跳跃键让游戏人物快速向前贴地滑动一段距离,Rockman的操作好像就是这样。再加一个约束条件,蹲下时按下左右移动键无效果。哇哦,这样再看看上面那段简单的代码要变成什么样子了。
如何以一种条理清晰,便于维护的方式来实现以上需求呢? 以上需求的核心是状态的转换,自然而然,带有“状态”这个关键字的state模式首先成了思考方向。state模式将每个状态抽象成类,状态的转换由状态自己来操作,是一种比较灵活的实现方式。基于state模式,将代码重构如下(伪代码):
// 伪代码class IStatus{ Role* role; // 输入向上键 virtual void Up(); // 输入向下键 virtual void Down(); // 输入向左键 virtual void Left(); // 输入向右键 virtual void Right(); // 输入跳跃键 virtual void Jump(); // 输入攻击键 virtual void Attack(); // 动作完成后 virtula void Done();};class IdleState;{ Left() {role->setState(WalkState);} Right() {role->setState(WalkState);} Down() {role->setState(CrouchState);} Jump() {role->setState(JumpState);}}class WalkState{ Jump() {role->setState(JumpState);}}class JumpState;{}class CrouchState{ Jump() {role->setState(SlipState);}}class SlipState{}
Role类:
class Role{ IState _state; // 输入向上键 void Up() { _state.Up();} // 输入向下键 void Down() { _state.Down();} // 输入向左键 void Left() { _state.Left();} // 输入向右键 void Right() { _state.Right();} // 输入跳跃键 void Jump() { _state.Jump();} // 输入攻击键 void Attack() { _state.Attack();}}
这样用state模式来写,后期维护和加入新的状态也变得十分容易,加入新的状态只需要加入一个新的类,而不需变更以有的代码。