游戏状态机

来源:互联网 发布:高斯勒让德算法 编辑:程序博客网 时间:2024/06/06 00:33


比如有这么一个"记忆"类的比赛游戏。你和电脑对战,轮到谁的回合,谁翻两张牌,如果两张牌一样,就消掉这两张牌,得2分,可以继续翻牌,如果两张牌不一样,就换一个人。直到最后,看谁的得分高。

先把图画出来会清晰些:


2.先看下不好的设计方式


我们来设计游戏大致架构,用一个圈表示一个状态。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. typedef enum{  
  2.     WaitingPlayer,  
  3.     CheckPlayer,  
  4.     AIThink,  
  5.     AIFirstCard,  
  6.    AISecondCard,  
  7.     CheckAI  
  8. }MatchGameState;  

准备一个_state的变量来记录当前的状态,然后放到update函数里,执行下面的伪代码。


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void MatchLayer::update(float dt){  
  2.     if(allCards.size() == 0){  
  3.         _state = GameOver;  
  4.     }  
  5.   
  6.   
  7.     switch(_state){  
  8.     case WaitingPlayer:  
  9.         if(cardCount == 2){  
  10.             _state = CheckPlayer;  
  11.             cardCount = 0;  
  12.         }  
  13.         break;  
  14.     case CheckPlayer:  
  15.         if(playerCard1 == playerCard2){  
  16.           玩家得分  
  17.             _state = WaitingPlayer;  
  18.         }else{  
  19.             _state = AIThink;  
  20.          把玩家点开的卡加入到记忆数组中  
  21.         }  
  22.         break;  
  23.     case AIThink:  
  24.         从记忆的数组中找两张相同的,找不到就随机准备两种卡  
  25.       _state = AIFirstCard;  
  26.         break;  
  27.     case AIFirstCard:  
  28.         点开第一张卡  
  29.         如果之前没找到相同卡,把这卡加入到记忆数组  
  30.         _state = AISecondCard;  
  31.         break;  
  32.     case AISecondCard:  
  33.         从记忆的数组中找两张相同的:  
  34.         如果找到跟第一张一样,就点开它,找不到就点刚开始的随机第2张,并且把第2张加入到记忆数组中。  
  35.         _state = CheckAI;  
  36.         break;  
  37.     case CheckAI:  
  38.         if(AICard1 == AICard2){  
  39.             _state = AIThink;  
  40.          电脑得分  
  41.         }else{  
  42.             _state = WaitingPlayer;  
  43.         }  
  44.     }  
  45. }  

这样写是可以,但是随着代码行数增加和业务逻辑变得复杂,后续会比较难维护。


3.使用"设计模式"来重构


我们来看下如何重构,使用"设计模式"来重构它。我盗了一张图来说明。



不知道这方法是设计模式中的哪种。

我们打算把所有的状态都用一个类来实现,它们都继承一个基类叫MatchState,它非常简单。有一个类来管理所有的状态。 MatchState如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #ifndef _MATCHSTATE_  
  2. #define _MATCHSTATE_  
  3.   
  4.   
  5. class MatchState{  
  6.     public:  
  7.     virtual void Update() = 0;  
  8.   
  9. };  
  10.   
  11. #endif  

我这里就没加OnEnter和OnExit了。简单起见。

为了简单些,就把Layer作为状态管理类,在Layer中增加一个属性,来表示当前状态:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. MatchState* currentState;  

在主要的Layer中增加一个方法来切换当前状态:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void changeState(MatchState* state){  
  2.     delete currentState;  
  3.     currentState = state;  
  4. }  

在update中就简单了,一直执行当前状态的Update方法:


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void MatchLayer::update(float dt){  
  2.     if(allCards.size() == 0){  
  3.         _state = GameOver;  
  4.     }  
  5.     currentState->Update();  
  6. }  

每个状态的具体业务逻辑都写在自己的类中。比如WaitingPlayerState类:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #ifndef _WAITINGPLAYERSTATE_H  
  2. #define _WAITINGPLAYERSTATE_H  
  3.   
  4. #include "MatchState.h"  
  5.   
  6. class WaitingPlayerState : public MatchState{  
  7. public:  
  8.     WaitingPlayerState(){  
  9.    }  
  10.     void Update(){  
  11.       if(sGlobal->cardCount == 2){  
  12.             sGlobal->matchLayer->changeState(new CheckPlayerCardsState());  
  13.         }  
  14.    }  
  15. };  
  16.   
  17.   
  18. #endif  

这里sGlobal是一个单例。

再比如CheckPlayerCardsState

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #ifndef _CHECKPLAYERCARDSSTATE_H  
  2. #define _CHECKPLAYERCARDSSTATE_H  
  3.   
  4. #include "MatchState.h"  
  5.   
  6. class CheckPlayerCardsState : public MatchState  
  7. {  
  8. public:  
  9.     void Update(){  
  10.       //非常复杂的具体业务逻辑写在这里  
  11.       if(playerCard1 == playerCard2){  
  12.           玩家得分  
  13.             sGlobal->matchLayer->changeState(new WaitingPlayerState ());  
  14.         }else{  
  15.             sGlobal->matchLayer->changeState(new AIThinkState ());  
  16.          把玩家点开的卡加入到记忆数组中  
  17.         }  
  18.   
  19.   }  
  20.   
  21. };  
  22.   
  23.   
  24. #endif  

其他状态类就不写出来了,总之通过这样把一个状态用一个类来表示,大大的使代码简洁些,扩展性强些。

0 0
原创粉丝点击