【Cocos2d-x状态机篇】第04章--事件驱动,你想象不到的强大
笨木头花心贡献,啥?花心?不呢,是用心~
转载请注明,原文地址:http://blog.csdn.net/musicvs/article/details/8349314
正文:
到现在为止,我们已经有能力实现简单的有限状态机了,但是,大家有没有发现一个问题?
(旁白:貌似我不止发现一个问题==)
那就是,我们必须主动地在update函数里检测状态的变化,但是,并不是每时每刻对象的状态都会改变的。这种主动检测状态改变的方式,其实十分不妥当,这会造成CUP的压力比较大。
(旁白:是CPU吧?拜托~!)
那么,现在我要向大家介绍一种十分美妙的技术,这无论是在应用开发还是游戏开发里,都是十分有用的。那就是,事件驱动。
很幸运,在Cocos2d-x里,已经集成了实现事件驱动的功能了,那就是CCNotificationCenter类,我更喜欢称之为“消息派发”。我建议对这方面不熟悉的朋友,先百度一下“观察者模式”、“订阅者”等关键词,以及了解关于CCNotificationCenter的使用。
那么,现在,我们要把我们第02章里的程序改为消息驱动的形式了~!
(旁白:废话终于结束了。。。)
1.彻底抛弃update函数,新的状态机
/*
文件名: MutouTFSM.h
描 述: 木头对象的状态机,用来管理状态
创建人: 笨木头 (CSDN博客:http://blog.csdn.net/musicvs)
创建日期: 2012.12.19
修改日期: 2012.12.20
*/
#ifndef __MUTOUT_FSM_H__
#define __MUTOUT_FSM_H__
#include "cocos2d.h"
USING_NS_CC;
class I_State;
class MutouT;
class MutouTFSM : public CCNode {
public:
~MutouTFSM();
static MutouTFSM* createWithMutouT(MutouT* mutou);
bool initWithMutouT(MutouT* mutou);
void changeState(I_State* state); /* 切换状态 */
private:
void onRecvWantToRest(CCObject* obj);
void onRecvWantToWriteCode(CCObject* obj);
void onRecvWantToWriteArticle(CCObject* obj);
/* 存放当前状态类 */
I_State* mCurState;
/* 木头对象 */
MutouT* mMutou;
};
#endif
这是新的状态机类,发现什么不一样了吗?
(旁白:不就是update函数删掉了,然后多了一个析构函数么==很特别吗?)
这其实不是重点(可恶的旁白竟然答对了),重点看看cpp文件:
#include "MutouTFSM.h"
#include "MutouT.h"
#include "I_State.h"
#include "EnumMsgType.h"
#define NOTIFY CCNotificationCenter::sharedNotificationCenter()
MutouTFSM::~MutouTFSM() {
NOTIFY->removeObserver(this, "wantToRest");
NOTIFY->removeObserver(this, "wantToWriteCode");
NOTIFY->removeObserver(this, "wantToWriteArticle");
}
MutouTFSM* MutouTFSM::createWithMutouT( MutouT* mutou ) {
MutouTFSM* fsm = new MutouTFSM();
if(fsm && fsm->initWithMutouT(mutou)) {
fsm->autorelease();
}
else {
CC_SAFE_DELETE(fsm);
fsm = NULL;
}
return fsm;
}
bool MutouTFSM::initWithMutouT( MutouT* mutou ) {
this->mCurState = NULL;
this->mMutou = mutou;
mMutou->retain();
/* 订阅消息 */
NOTIFY->addObserver(this,callfuncO_selector(MutouTFSM::onRecvWantToRest) , "wantToRest", NULL);
NOTIFY->addObserver(this,callfuncO_selector(MutouTFSM::onRecvWantToWriteCode) , "wantToWriteCode", NULL);
NOTIFY->addObserver(this,callfuncO_selector(MutouTFSM::onRecvWantToWriteArticle) , "wantToWriteArticle", NULL);
return true;
}
void MutouTFSM::changeState( I_State* state ) {
CC_SAFE_DELETE(mCurState);
this->mCurState = state;
}
void MutouTFSM::onRecvWantToRest( CCObject* obj ) {
this->mCurState->execute(mMutou, en_Msg_WantToRest);
}
void MutouTFSM::onRecvWantToWriteCode( CCObject* obj ) {
this->mCurState->execute(mMutou, en_Msg_WantToWriteCode);
}
void MutouTFSM::onRecvWantToWriteArticle( CCObject* obj ) {
this->mCurState->execute(mMutou, en_Msg_WantToWriteArticle);
}
首先,为了避免代码过长,我做了一个宏定义:
#defineNOTIFYCCNotificationCenter::sharedNotificationCenter()
我们来看看,状态机总共订阅了3个消息:wangToRest、wantToWriteCode、wantToWriteArticle。为了偷懒,我直接把字符串硬编码了。
再来看看收到消息后,状态机是怎么处理的:
void MutouTFSM::onRecvWantToRest( CCObject* obj ) {
this->mCurState->execute(mMutou, en_Msg_WantToRest);
}
很简单,很以前一样,调用当前状态的execute方法,但这次多了一个参数,那就是消息类型。看看消息类型的枚举:
#ifndef __ENUM_MSG_TYPE_H__
#define __ENUM_MSG_TYPE_H__
enum EnumMsgType {
en_Msg_WantToRest,
en_Msg_WantToWriteCode,
en_Msg_WantToWriteArticle,
};
#endif
(旁白:感觉好麻烦==)
大家没有觉得这样好麻烦吗?
(旁白:你就忽略我吧,我习惯了。。。)
其实我也觉得好麻烦,但是Cocos2d-x的CCNotificationCenter只能这么用,它是使用函数指针的方式来回调进行消息发布,而不是使用接口。所以状态机每订阅一个消息就要多写一个函数来接收消息。
更可恶的是,订阅消息的时候,消息类型只能用字符串,不能用枚举值~!所以状态机在传递事件给状态类的时候,只能再建一套枚举类型了。如果大家有更好的方法记得和我分享~
2.状态类做小改动
然后,状态类的execute函数也要增加一个参数,就是消息类型:
/*
文件名: State.h
描 述: 状态基类
创建人: 笨木头 (CSDN博客:http://blog.csdn.net/musicvs)
创建日期: 2012.12.17
修改日期: 2012.12.20
*/
#ifndef __I_STATE_H__
#define __I_STATE_H__
#include "EnumMsgType.h"
class MutouT;
class I_State {
public:
virtual void execute(MutouT* mutou, EnumMsgType enMsgType) = 0;
};
#endif
很简单,不解释了,其它的状态子类也要做相应的修改。
然后,我们来看看3种状态类的execute函数的新实现:
void StateRest::execute( MutouT* mutou, EnumMsgType enMsgType ) {
switch(enMsgType) {
case en_Msg_WantToWriteCode:
mutou->writeCode();
mutou->getFSM()->changeState(new StateWirteCode());
break;
case en_Msg_WantToWriteArticle:
mutou->writeArticle();
mutou->getFSM()->changeState(new StateWriteArticle());
break;
}
}
void StateWriteArticle::execute( MutouT* mutou, EnumMsgType enMsgType ) {
switch(enMsgType) {
case en_Msg_WantToRest:
mutou->rest();
mutou->getFSM()->changeState(new StateRest());
break;
}
}
void StateWirteCode::execute( MutouT* mutou, EnumMsgType enMsgType ) {
switch(enMsgType) {
case en_Msg_WantToRest:
mutou->rest();
mutou->getFSM()->changeState(new StateRest());
break;
}
}
看到了吗?
(旁白:看到了,看到了==你快点说吧,别这么唠叨了。。。)
每个状态类的execute函数已经不需要再主动去判断木头当前在做什么或者想做什么或者是什么状况。它只要知道当前发生了什么事件,它只关心它所关心的事件,在特定的事件下改变木头的状态即可。
3.测试新代码
是时候测试一下了,继续修改HelloWorld的init函数:
bool HelloWorld::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(! CCLayer::init());
/* 新建木头2角色 */
mMutou = MutouT::create();
/* 初始化木头的状态为休息 */
mMutou->getFSM()->changeState(new StateRest());
/* 模拟事件的发生 */
NOTIFY->postNotification("wantToWriteCode");
NOTIFY->postNotification("wantToRest");
NOTIFY->postNotification("wantToWriteArticle");
NOTIFY->postNotification("wantToRest");
NOTIFY->postNotification("wantToWriteArticle");
NOTIFY->postNotification("wantToRest");
NOTIFY->postNotification("wantToWriteCode");
NOTIFY->postNotification("wantToRest");
this->addChild(mMutou);
bRet = true;
} while (0);
return bRet;
}
由于事件的发生是随机的,要产生这些随机的事件,可不是一两段代码就能搞定的,所以,我直接用最简单的方式,主动发送这些事件消息。
然后,用debug模式运行项目,我们将看到熟悉的日志:
mutouiswirtingCode.
mutouisresting.
mutouiswritingarticle.
mutouisresting.
mutouiswritingarticle.
mutouisresting.
mutouiswirtingCode.
mutouisresting.
4.最后的最后
好了,我想,我对有限状态机的介绍就到这里了,其实我对有限状态机的理解也不是十分深刻,我也是现学现卖,一方面是巩固自己的知识,另一方面我确实很喜欢和大家分享知识。
也许通过这四篇文章的介绍大家还是无法感受到状态机倒底有多强大,但我可以肯定地告诉大家,它真的很强大。比如,我描述这样一个场景:
一个拥有超能力的木头,它所经过的地方周围的物体都会弹开,于是,它一遍走,周围的物体一遍弹。十分美妙的场景~
(旁白:美你个木头==。。。)
这依旧可以使用有限状态机来实现,木头经过的时候可以发出“我来了,我的能力范围是方圆10米”,然后周围的物体订阅了这个消息,在接收到这个消息的时候,就由静止状态改变为弹射状态,然后物体就执行弹射动作。而这一切看起来就像是自动完成的。
好了,已经对状态机理解深刻的朋友,希望能给我一些建议,对状态机不了解的朋友,希望能帮到你们。好吧,旁白出来清场~
(旁白:清你可爱的妹纸的==)
(旁白:OK,大家排好队,一个接一个地离场...额,我为毛要帮他清场啊~!)