上一篇文章寫完之后晰韵,我一直在想做一個游戲示例链瓦,想將之前學(xué)到的狀態(tài)機模式運用在其中拆魏。然而一直遇到各種奇怪的bug(之后寫出來),所以一直沒有完成這件事。而且加之自己又犯懶了渤刃,所以我決定做一件更簡單的事拥峦,以更容易接近的目標(biāo)來引誘懶的不行的自己。
廢話說完溪掀,來說正經(jīng)事了事镣。前幾天認(rèn)真看了一篇 博客-游戲狀態(tài)機(侵立刪),覺得挺不錯的揪胃,建議盆友們認(rèn)真看一看璃哟。
實現(xiàn)一個UI切換示例
為比較心急的伙伴放一個示例的Github鏈接
讀完博客之后,之前計劃的示例貌似有了頭緒喊递,(廢話型)總結(jié)出來随闪,實現(xiàn)一個游戲狀態(tài)機結(jié)構(gòu)需要進行如下幾步:
- 分析示例(需求),劃分狀態(tài)骚勘。
- 找出需求中的狀態(tài)铐伴、事件。
- 寫一個狀態(tài)轉(zhuǎn)換表俏讹,使得需求更加明確当宴。
- 寫一個控制類,包含狀態(tài)轉(zhuǎn)換方法泽疆,作為所有狀態(tài)的根户矢,所有的狀態(tài)在這個根之上切換。
- 寫一個狀態(tài)基類殉疼,然后派生出需要的狀態(tài)類梯浪。
- 在每個狀態(tài)類中,寫好需要的事件瓢娜,以及事件發(fā)生的處理(就是進行狀態(tài)轉(zhuǎn)換)挂洛。
分析需求,劃分狀態(tài)
我要做一個簡單的UI切換示例眠砾,所以簡述需求就是: 打開程序進入主界面虏劲,主界面中點擊開始游戲,進入游戲界面褒颈,游戲界面中點擊結(jié)束游戲伙单,進入結(jié)算界面,結(jié)算界面可以點擊回到主界面哈肖,進入主界面,也可以點擊重新開始游戲念秧,再次進入游戲界面淤井。
找出狀態(tài)和事件
從需求描述來看,狀態(tài)有三個:
- 主界面
- 游戲界面
- 結(jié)算界面
事件有四個:
- 主界面->點擊開始游戲按鈕
- 游戲界面->點擊游戲結(jié)束按鈕
- 結(jié)算界面->點擊回到主頁按鈕
- 結(jié)算界面->點擊重新游戲按鈕
狀態(tài)轉(zhuǎn)換表
然后(裝模作樣的)畫了一個狀態(tài)轉(zhuǎn)換表:
狀態(tài) | 事件 | 下一狀態(tài) |
---|---|---|
主界面 | 點擊開始游戲 | 游戲界面 |
游戲界面 | 點擊游戲結(jié)束 | 結(jié)算界面 |
結(jié)算界面 | 點擊回到主界面 | 主界面 |
點擊重新游戲 | 游戲界面 |
寫一個控制類
之前一篇文章中有提到過的,在Cocos2d-x游戲開發(fā)中币狠,剛好使用Scene來作為控制器游两,由Scene來控制各層的出現(xiàn)和消失。
這里貼出Scene的源碼(完整的源文件漩绵,包括了后續(xù)步驟才會出現(xiàn)的狀態(tài)基類贱案,以及派生的狀態(tài)類):
QFLGameTwoScene.hpp
//
// QFLGameTwoScene.hpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#ifndef QFLGameTwoScene_hpp
#define QFLGameTwoScene_hpp
#include <stdio.h>
#include "cocos2d.h"
#include "QFLGameTwoStateBase.hpp"
class QFLGameTwoScene : public cocos2d::Scene
{
CC_CONSTRUCTOR_ACCESS:
QFLGameTwoScene();
virtual ~QFLGameTwoScene();
virtual bool init();
CREATE_FUNC(QFLGameTwoScene);
public:
void addUI(); //添加UI,黑色背景
void initGame(); //初始化游戲止吐,進入一個初始狀態(tài)
void addEventListener(); //添加事件監(jiān)聽宝踪,需要切換狀態(tài)時通過發(fā)送自定義事件來實現(xiàn)
//切換狀態(tài)的函數(shù)
void ChangeState(GameTwoState* pState);
private:
GameTwoState* m_pCurrentState; //當(dāng)前狀態(tài)
};
#endif /* QFLGameTwoScene_hpp */
QFLGameTwoScene.cpp
//
// QFLGameTwoScene.cpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#include "QFLGameTwoScene.hpp"
#include "QFLTools/QFLHelper.hpp"
//基類
#include "QFLGameTwoStateBase.hpp"
//狀態(tài)類
#include "QFLGameTwoMainLayer.hpp"
#include "QFLGameTwoGameLayer.hpp"
#include "QFLGameTwoResultLayer.hpp"
USING_NS_CC;
QFLGameTwoScene::QFLGameTwoScene()
{
}
QFLGameTwoScene::~QFLGameTwoScene()
{
}
bool QFLGameTwoScene::init()
{
if (!Scene::init()) {
return false;
}
else {
this->addUI();
this->addEventListener();
this->initGame();
return true;
}
}
void QFLGameTwoScene::addUI()
{
//黑色背景
auto pLayer = QFL_HELPER->getColorfulLayer();
QFL_HELPER->addNoTouchListener(pLayer);
this->addChild(pLayer);
}
void QFLGameTwoScene::initGame()
{
//進入一個初始狀態(tài)
log("游戲開始");
m_pCurrentState = QFLGameTwoMainLayer::create();
m_pCurrentState->Enter(this);
}
void QFLGameTwoScene::addEventListener()
{
Director::getInstance()->getEventDispatcher()->addCustomEventListener(GameTwoEvent::Main_Start, [=](EventCustom* pEvent){
log("[Event]->Main_Start");
this->ChangeState(QFLGameTwoGameLayer::create());
});
Director::getInstance()->getEventDispatcher()->addCustomEventListener(GameTwoEvent::Game_Over, [=](EventCustom* pEvent){
log("[Event]->Game_Over");
this->ChangeState(QFLGameTwoResultLayer::create());
});
Director::getInstance()->getEventDispatcher()->addCustomEventListener(GameTwoEvent::Result_Home, [=](EventCustom* pEvent){
log("[Event]->Result_Home");
this->ChangeState(QFLGameTwoMainLayer::create());
});
Director::getInstance()->getEventDispatcher()->addCustomEventListener(GameTwoEvent::Result_Replay, [=](EventCustom* pEvent){
log("[Event]->Result_Replay");
this->ChangeState(QFLGameTwoGameLayer::create());
});
}
void QFLGameTwoScene::ChangeState(GameTwoState *pState)
{
//舊狀態(tài)退出
m_pCurrentState->Exit(this);
//保存新狀態(tài)
m_pCurrentState = pState;
//新狀態(tài)進入
m_pCurrentState->Enter(this);
}
QFLTools/QFLHelper.hpp 這個文件是作為一個工具類存在的,本示例所有源碼在我的Github中
上面可以看到碍扔,Scene中主要包含了:
-
void ChangeState(GameTwoState* pState);
用于狀態(tài)切換 -
GameTwoState* m_pCurrentState;
用于記錄當(dāng)前狀態(tài) -
void addEventListener();
用于監(jiān)聽切換狀態(tài)事件 -
void initGame();
用于進入初始狀態(tài)
包含了以上內(nèi)容瘩燥,本示例中的這個Scene就能起到控制器的作用了。
寫一個狀態(tài)基類
狀態(tài)基類是很有必要的不同,為了達到各個同級狀態(tài)都能通過同一個控制器來進行狀態(tài)轉(zhuǎn)換的目標(biāo)厉膀,各個狀態(tài)類進行切換時必須有一套統(tǒng)一的流程。
這里貼出本示例中狀態(tài)基類的源碼:
QFLGameTwoStateBase.hpp
//
// QFLGameTwoStateBase.hpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#ifndef QFLGameTwoStateBase_hpp
#define QFLGameTwoStateBase_hpp
#include <stdio.h>
#include "cocos2d.h"
//State的基類
class GameTwoState
{
public:
virtual ~GameTwoState() {}
virtual void Enter(cocos2d::Scene* pLayer) = 0;
virtual void Execute() = 0;
virtual void Exit(cocos2d::Scene* pLayer) = 0;
};
#define SC(__type__) static const __type__
//游戲中的事件(通過事件來進行狀態(tài)切換)
namespace GameTwoEvent {
SC(std::string) Main_Start = "Event_Main_Start"; //[Main]開始游戲的事件
SC(std::string) Game_Over = "Event_Game_Over"; //[Game]結(jié)束游戲的事件
SC(std::string) Result_Home = "Event_Result_Home"; //[Result]結(jié)算界面回主頁
SC(std::string) Result_Replay = "Event_Result_Replay";//[Result]結(jié)算界面重新開始
}
#endif /* QFLGameTwoStateBase_hpp */
這個源文件中包含了:
-
class GameTwoState
狀態(tài)基類 -
virtual void Enter(cocos2d::Scene* pLayer) = 0;
狀態(tài)進入的處理 -
virtual void Execute() = 0;
本狀態(tài)執(zhí)行的方法 -
virtual void Exit(cocos2d::Scene* pLayer) = 0;
狀態(tài)退出的處理 -
namespace GameTwoEvent
包含狀態(tài)轉(zhuǎn)換事件的namespace
所有的派生狀態(tài)類都會include
這個文件二拐,實現(xiàn)虛基類GameTwoState
中的方法服鹅,所以所有的狀態(tài)類都有統(tǒng)一的切換流程:
- last_state->exit
- current_state->enter
- current_state->execute
而且在完成某個事件,需要進行狀態(tài)轉(zhuǎn)換時百新,可以通過發(fā)送自定義事件來完成消息的傳遞企软,自定義事件名放在這里也是為了方便起見。
每個狀態(tài)類中寫好需要的事件及處理
這里直接貼出幾個State的源碼了吟孙,其中包含注釋澜倦,廢話就不重復(fù)再寫了:
MainState源碼
QFLGameTwoMainLayer.hpp
//
// QFLGameTwoMainLayer.hpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#ifndef QFLGameTwoMainLayer_hpp
#define QFLGameTwoMainLayer_hpp
#include <stdio.h>
#include "cocos2d.h"
#include "QFLGameTwoStateBase.hpp"
class QFLGameTwoMainLayer : public cocos2d::Layer, public GameTwoState
{
public:
QFLGameTwoMainLayer();
virtual ~QFLGameTwoMainLayer();
virtual bool init() override;
CREATE_FUNC(QFLGameTwoMainLayer);
//實現(xiàn)StateBase的狀態(tài)切換所需方法
void Enter(cocos2d::Scene* pLayer) override;
void Execute() override;
void Exit(cocos2d::Scene* pLayer) override;
private:
void addUI(); //添加背景UI,添加按鈕等
};
#endif /* QFLGameTwoMainLayer_hpp */
QFLGameTwoMainLayer.cpp
//
// QFLGameTwoMainLayer.cpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#include "QFLGameTwoMainLayer.hpp"
#include "QFLTools/QFLHelper.hpp"
USING_NS_CC;
QFLGameTwoMainLayer::QFLGameTwoMainLayer()
{
}
QFLGameTwoMainLayer::~QFLGameTwoMainLayer()
{
}
bool QFLGameTwoMainLayer::init()
{
if (!Layer::init()) {
return false;
}
else {
this->addUI();
return true;
}
}
void QFLGameTwoMainLayer::Enter(cocos2d::Scene* pLayer)
{
log("<Enter>\t\t->MainLayer");
//添加到Scene中
pLayer->addChild(this);
//執(zhí)行各種事件
this->Execute();
}
void QFLGameTwoMainLayer::Execute()
{
log("<Execute>\t->MainLayer"); //執(zhí)行內(nèi)容只是打印一個Log
}
void QFLGameTwoMainLayer::Exit(cocos2d::Scene* pLayer)
{
log("<Exit>\t\t->MainLayer\n");
//移除
this->removeFromParentAndCleanup(true);
}
void QFLGameTwoMainLayer::addUI()
{
//黑色背景
auto pLayer = QFL_HELPER->getColorfulLayer();
QFL_HELPER->addNoTouchListener(pLayer);
this->addChild(pLayer);
//MainLogo
auto pLogo = Label::createWithSystemFont("MainLayer", "", 50);
pLogo->setPosition(Vec2(SCREEN_CENTER.x, SCREEN_CENTER.y + 60));
this->addChild(pLogo);
//菜單
auto pMenu = Menu::create();
pMenu->setPosition(Vec2::ZERO);
this->addChild(pMenu);
//按鈕杰妓,點擊發(fā)送游戲開始事件
auto pStart = MenuItemFont::create("Start", [=](cocos2d::Ref* pRef){
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GameTwoEvent::Main_Start);
});
pStart->setPosition(Vec2(SCREEN_CENTER.x, SCREEN_CENTER.y - 100));
pMenu->addChild(pStart);
}
GameState源碼
QFLGameTwoGameLayer.hpp
//
// QFLGameTwoGameLayer.hpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#ifndef QFLGameTwoGameLayer_hpp
#define QFLGameTwoGameLayer_hpp
#include <stdio.h>
#include "cocos2d.h"
#include "QFLGameTwoStateBase.hpp"
class QFLGameTwoGameLayer : public cocos2d::Layer, public GameTwoState
{
public:
QFLGameTwoGameLayer();
virtual ~QFLGameTwoGameLayer();
virtual bool init() override;
CREATE_FUNC(QFLGameTwoGameLayer);
//實現(xiàn)StateBase的狀態(tài)切換所需方法
void Enter(cocos2d::Scene* pLayer) override;
void Execute() override;
void Exit(cocos2d::Scene* pLayer) override;
private:
void addUI();
};
#endif /* QFLGameTwoGameLayer_hpp */
QFLGameTwoGameLayer.cpp
//
// QFLGameTwoGameLayer.cpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#include "QFLGameTwoGameLayer.hpp"
#include "QFLTools/QFLHelper.hpp"
USING_NS_CC;
QFLGameTwoGameLayer::QFLGameTwoGameLayer()
{
}
QFLGameTwoGameLayer::~QFLGameTwoGameLayer()
{
}
bool QFLGameTwoGameLayer::init()
{
if (!Layer::init()) {
return false;
}
else {
this->addUI();
return true;
}
}
void QFLGameTwoGameLayer::addUI()
{
//黑色背景
auto pLayer = QFL_HELPER->getColorfulLayer();
QFL_HELPER->addNoTouchListener(pLayer);
this->addChild(pLayer);
//GameLogo
auto pLogo = Label::createWithSystemFont("GameLayer", "", 50);
pLogo->setPosition(Vec2(SCREEN_CENTER.x, SCREEN_CENTER.y + 60));
this->addChild(pLogo);
//菜單
auto pMenu = Menu::create();
pMenu->setPosition(Vec2::ZERO);
this->addChild(pMenu);
//按鈕藻治,點擊發(fā)送游戲結(jié)束事件
auto pOver = MenuItemFont::create("GameOver", [=](cocos2d::Ref* pRef){
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GameTwoEvent::Game_Over);
});
pOver->setPosition(Vec2(SCREEN_CENTER.x, SCREEN_CENTER.y - 100));
pMenu->addChild(pOver);
}
void QFLGameTwoGameLayer::Enter(cocos2d::Scene* pLayer)
{
log("<Enter>\t\t->GameLayer");
//添加到Scene中
pLayer->addChild(this);
//執(zhí)行游戲
this->Execute();
}
void QFLGameTwoGameLayer::Execute()
{
log("<Execute>\t->GameLayer");
}
void QFLGameTwoGameLayer::Exit(cocos2d::Scene* pLayer)
{
log("<Exit>\t\t->GameLayer\n");
//移除游戲
this->removeFromParentAndCleanup(true);
}
ResultState源碼
QFLGameTwoResultLayer.hpp
//
// QFLGameTwoResultLayer.hpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#ifndef QFLGameTwoResultLayer_hpp
#define QFLGameTwoResultLayer_hpp
#include <stdio.h>
#include "cocos2d.h"
#include "QFLGameTwoStateBase.hpp"
class QFLGameTwoResultLayer : public cocos2d::Layer, public GameTwoState
{
public:
QFLGameTwoResultLayer();
virtual ~QFLGameTwoResultLayer();
virtual bool init() override;
CREATE_FUNC(QFLGameTwoResultLayer);
//實現(xiàn)StateBase的狀態(tài)切換所需方法
void Enter(cocos2d::Scene* pLayer) override;
void Execute() override;
void Exit(cocos2d::Scene* pLayer) override;
private:
void addUI();
};
#endif /* QFLGameTwoResultLayer_hpp */
QFLGameTwoResultLayer.cpp
//
// QFLGameTwoResultLayer.cpp
// QFLTest
//
// Created by QuFangliu on 16/10/8.
//
//
#include "QFLGameTwoResultLayer.hpp"
#include "QFLTools/QFLHelper.hpp"
USING_NS_CC;
QFLGameTwoResultLayer::QFLGameTwoResultLayer()
{
}
QFLGameTwoResultLayer::~QFLGameTwoResultLayer()
{
}
bool QFLGameTwoResultLayer::init()
{
if (!Layer::init()) {
return false;
}
else {
this->addUI();
return true;
}
}
void QFLGameTwoResultLayer::addUI()
{
//黑色背景
auto pLayer = QFL_HELPER->getColorfulLayer();
QFL_HELPER->addNoTouchListener(pLayer);
this->addChild(pLayer);
//GameLogo
auto pLogo = Label::createWithSystemFont("ResultLayer", "", 50);
pLogo->setPosition(Vec2(SCREEN_CENTER.x, SCREEN_CENTER.y + 60));
this->addChild(pLogo);
//菜單
auto pMenu = Menu::create();
pMenu->setPosition(Vec2::ZERO);
this->addChild(pMenu);
//按鈕,點擊發(fā)送會到主界面事件
auto pHome = MenuItemFont::create("Home", [=](cocos2d::Ref* pRef){
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GameTwoEvent::Result_Home);
});
pHome->setPosition(Vec2(SCREEN_CENTER.x, SCREEN_CENTER.y - 100));
pMenu->addChild(pHome);
//按鈕巷挥,點擊發(fā)送重新開始游戲事件
auto pReplay = MenuItemFont::create("Replay", [=](cocos2d::Ref* pRef){
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GameTwoEvent::Result_Replay);
});
pReplay->setPosition(Vec2(SCREEN_CENTER.x, SCREEN_CENTER.y - 150));
pMenu->addChild(pReplay);
}
void QFLGameTwoResultLayer::Enter(cocos2d::Scene* pLayer)
{
log("<Enter>\t\t->ResultLayer");
//添加到Scene中
pLayer->addChild(this);
//執(zhí)行游戲
this->Execute();
}
void QFLGameTwoResultLayer::Execute()
{
log("<Execute>\t->ResultLayer");
}
void QFLGameTwoResultLayer::Exit(cocos2d::Scene* pLayer)
{
log("<Exit>\t\t->ResultLayer\n");
//移除游戲
this->removeFromParentAndCleanup(true);
}
到這里桩卵,上面所提到的步驟全部進行完了。這個UI切換的狀態(tài)機示例也就完成了倍宾。
運行效果
在AppDelegate.cpp中雏节,直接進入Scene中:
auto pScene = QFLGameTwoScene::create();
director->runWithScene(pScene);
進入主界面:
此時的Log:
游戲開始
<Enter> ->MainLayer
<Execute> ->MainLayer
點擊Start,進入游戲界面:
此時的Log:
游戲開始
<Enter> ->MainLayer
<Execute> ->MainLayer
[Event]->Main_Start
<Exit> ->MainLayer
<Enter> ->GameLayer
<Execute> ->GameLayer
點擊Over高职,進入結(jié)算界面:
此時的Log:
游戲開始
<Enter> ->MainLayer
<Execute> ->MainLayer
[Event]->Main_Start
<Exit> ->MainLayer
<Enter> ->GameLayer
<Execute> ->GameLayer
[Event]->Game_Over
<Exit> ->GameLayer
<Enter> ->ResultLayer
<Execute> ->ResultLayer
點擊Replay钩乍,重新進入游戲界面:
此時的Log:
游戲開始
<Enter> ->MainLayer
<Execute> ->MainLayer
[Event]->Main_Start
<Exit> ->MainLayer
<Enter> ->GameLayer
<Execute> ->GameLayer
[Event]->Game_Over
<Exit> ->GameLayer
<Enter> ->ResultLayer
<Execute> ->ResultLayer
[Event]->Result_Replay
<Exit> ->ResultLayer
<Enter> ->GameLayer
<Execute> ->GameLayer
點擊Over,再次進入結(jié)算界面怔锌,然后在結(jié)算界面點擊Home寥粹,回到主界面
上面的兩次操作完成后变过,Log如下:
游戲開始
<Enter> ->MainLayer
<Execute> ->MainLayer
[Event]->Main_Start
<Exit> ->MainLayer
<Enter> ->GameLayer
<Execute> ->GameLayer
[Event]->Game_Over
<Exit> ->GameLayer
<Enter> ->ResultLayer
<Execute> ->ResultLayer
[Event]->Result_Replay
<Exit> ->ResultLayer
<Enter> ->GameLayer
<Execute> ->GameLayer
[Event]->Game_Over
<Exit> ->GameLayer
<Enter> ->ResultLayer
<Execute> ->ResultLayer
[Event]->Result_Home
<Exit> ->ResultLayer
<Enter> ->MainLayer
<Execute> ->MainLayer
總結(jié)
這里用FSM實現(xiàn)UI切換示例只是眾多方法中的一種,暫時還沒有去比較它們的優(yōu)劣涝涤。(使用markdown編輯器的經(jīng)驗值+1)
例如:在狀態(tài)基類文件中媚狰,添加一個狀態(tài)枚舉類型
enum StateType
,void ChangeState
轉(zhuǎn)換狀態(tài)時不再傳遞一個GameTwoState *pState
指針阔拳,而是傳遞一個狀態(tài)枚舉值StateType
崭孤,這種方法在某些情況下很有效。
例如:不用在
void Enter
中傳入一個cocos2d::Scene* pLayer
指針糊肠,而是在void ChangeState
中生成新的狀態(tài)類辨宠,并addChild
到Scene中。不用在void Exit
中傳入一個cocos2d::Scene* pLayer
指針罪针,直接使用removeFromParentAndClean
方法就能從Scene中刪除彭羹。
可能性多種多樣,如果有小伙伴比較過這些方法泪酱。求留言告知派殷,求分享經(jīng)驗。