笨木头花心贡献,啥?花心?不呢,是用心~
转载请注明,原文地址: http://blog.csdn.net/musicvs/article/details/8348323
正文:
状态机的应用当然就少不了状态模式了,因为它们都有“状态”两个字。
(旁白:总感觉这句话十分不可靠= =)
本章我们来简单地介绍一下状态模式,并且利用状态模式优化我们的有限状态机的实现。
1. 什么是状态模式
说实话,我不知道,但我知道怎么使用...
用一句话来概括,也许是这样:同一件事情,让不同的人去做,每个人都会用自己的方式来做事,但,同一时间,只会让一个人在做这件事情。由老板来决定某一刻由谁来做事。
(旁白:又你可爱的妹纸的,这明明是两句话,两个句话有没有= =)
用程序来说,也许是这样:同一个动作,让不同的类去执行,每个类都有自己独特的处理方式,至于由哪个类来执行这个动作,要根据当前的状态来判断。并且,不同的类执行完动作之后,会切换状态。
(旁白:还是没听懂。。。)
OK,我相信大家都明白了什么是状态模式了,接下来我们把第01章的例子稍微改改,很简单的~!
(旁白:等等,我压根儿就没搞懂什么是状态模式啊!)
好吧,我画了一个图,帮助大家理解:
图中有一个对象(蓝色方块),有三个状态处理类。
比如我有一件事情要做,那就是,起床之后要做的事情,但是由哪个类来执行这件事情呢?这要根据我的当前状态来决定(我有三种状态:肚子饿,想看电影,想吐槽),默认的状态是肚子饿。
然后,今天起床,我的状态是“肚子饿”。那我就会自动调用小木厨师来执行事件,执行完之后,状态会切换到“想看电影”。
那么,第二天,我起床的状态就是“想看电影”。那我就会自动调用电影院来执行事件,执行完之后,状态会切换到“想吐槽”。
第三天,我起床的状态就是“想吐槽”。那我就会自动调用吐槽旁白来执行事件,执行完之后,状态会切换到“肚子饿”。
第四天,我起床之后的状态就是“肚子饿”。那我就会自动调用小木厨师来执行事件,执行完之后,状态切换到“想看电影”。
第五天,我起床...(旁白:停~!!!你想说到天亮吗?!)
其实这个例子有点不准确,甚至是十分不准确,但是对于理解状态模式还是有点帮助了。
现在,大家在接着往下看之前,请确认你已经大概了解状态模式(起码去百度过,看过一篇正式介绍状态模式的文章)。因为接下来的代码和上面那张图的差别还是不小的,如果对状态模式不理解,就会产生混乱。
那么,用什么方法实现自动调用哪个类呢?当然是多态了,小木厨师、电影院、吐槽旁白这三个类都继承了同一个父类,它们就是我们要说的状态类了。
2. 用状态模式实现有限状态机
还记得上一章的Mutou类吗?兴趣爱好很广泛的那个~(旁白:我不想吐槽了...)大家应该对它的update函数印象很深刻吧?那我就不贴出来了:
void Mutou::update( float dt ) { /* 判断在每一种状态下应该做什么事情 */ switch(enCurState) { case enStateWriteCode: /* 如果累了就休息,并且切换到休息状态 */ if(isTire()) { rest(); changeState(enStateRest); } break; case enStateWriteArticle: /* 如果累了就休息,并且切换到休息状态 */ if(isTire()) { rest(); changeState(enStateRest); } break; case enStateRest: /* 一定的概率写代码,一定的概率写教程,并且切换到相应的状态 */ if(isWantToWriteArticle()) { writeArticle(); changeState(enStateWriteArticle); } else { writeCode(); changeState(enStateWriteCode); } break; } }
update函数会先判断木头当前所在的状态,然后再去执行相对应的逻辑。
(旁白:你刚刚说你不贴的代码的...)
那个旁白什么的,你刚刚也说你不想吐槽啊~!
但是啊,要是木头有好多好多状态,那这个函数也太庞大了~!是的,我们现在就要用到状态模式了,很简单的,别走神咯~!
3. 状态基类
首先,我们需要一个状态基类,它只有一个抽象方法execute,表示要执行一件事情。至于执行什么事情,由它的子类来决定。
/* 文件名: State.h 描 述: 状态基类 创建人: 笨木头 (CSDN博客:http://blog.csdn.net/musicvs) 创建日期: 2012.12.17 */ #ifndef __I_STATE_H__ #define __I_STATE_H__ class MutouT; class I_State { public: virtual void execute(MutouT* mutou) = 0; }; #endif
4 三种状态对应的状态类
接下来,我们要实现最核心的类:状态类。
木头有3种状态:写代码、写教程、休息。
/* 文件名: StateWirteCode.h 描 述: 写代码状态 创建人: 笨木头 (CSDN博客:http://blog.csdn.net/musicvs) 创建日期: 2012.12.17 */ #ifndef __STATE_WRITE_CODE_H__ #define __STATE_WRITE_CODE_H__ #include "I_State.h" class MutouT; class StateWirteCode : public I_State { public: virtual void execute( MutouT* mutou ); }; #endif /* cpp文件 */ #include "StateWirteCode.h" #include "StateRest.h" #include "MutouT.h" void StateWirteCode::execute( MutouT* mutou ) { /* 如果累了就休息,并且切换到休息状态 */ if(mutou->isTire()) { mutou->rest(); mutou->changeState(new StateRest()); } }
上面这个就是写代码状态类,它继承了I_State,拥有一个execute方法,它表示,在写代码状态下执行一个动作。
那么,接下来,另外两种状态的类也是一样的,唯一不同的就是execute的具体实现。
/* 文件名: StateWirteArticle.h 描 述: 写教程状态 创建人: 笨木头 (CSDN博客:http://blog.csdn.net/musicvs) 创建日期: 2012.12.17 */ #ifndef __STATE_WRITE_ARTICLE_H__ #define __STATE_WRITE_ARTICLE_H__ #include "I_State.h" class MutouT; class StateWriteArticle : public I_State { public: virtual void execute( MutouT* mutou ); }; #endif /* cpp文件 */ #include "StateWriteArticle.h" #include "StateRest.h" #include "MutouT.h" void StateWriteArticle::execute( MutouT* mutou ) { /* 如果累了就休息,并且切换到休息状态 */ if(mutou->isTire()) { mutou->rest(); mutou->changeState(new StateRest()); } }
/* 文件名: StateRest.h 描 述: 休息状态 创建人: 笨木头 (CSDN博客:http://blog.csdn.net/musicvs) 创建日期: 2012.12.17 */ #ifndef __STATE_REST_H__ #define __STATE_REST_H__ #include "I_State.h" class MutouT; class StateRest : public I_State { public: virtual void execute( MutouT* mutou ); }; #endif /* cpp文件 */ #include "StateRest.h" #include "StateWriteArticle.h" #include "StateWirteCode.h" #include "MutouT.h" void StateRest::execute( MutouT* mutou ) { /* 一定的概率写代码,一定的概率写教程,并且切换到相应的状态 */ if(mutou->isWantToWriteArticle()) { mutou->writeArticle(); mutou->changeState(new StateWriteArticle()); } else { mutou->writeCode(); mutou->changeState(new StateWirteCode()); } }
5. 新的木头类
为了使用状态模式,Mutou类要修改,先看看头文件:
#ifndef __MUTOU_T_H__ #define __MUTOU_T_H__ #include "cocos2d.h" USING_NS_CC; class I_State; class MutouT : public CCNode { public: CREATE_FUNC(MutouT); virtual bool init(); bool isTire(); /* 判断是否写代码写累了 */ bool isWantToWriteArticle(); /* 是否想写教程 */ void writeCode(); /* 写代码 */ void writeArticle(); /* 写教程 */ void rest(); /* 休息 */ void changeState(I_State* state); /* 切换状态 */ virtual void update(float dt); private: /* 存放当前状态类 */ I_State* mCurState; }; #endif
和以前的代码区别并不大,有两处修改:
1. changeState的参数变了,变成了I_State,同时,表示状态的枚举类也删掉了,因为已经有了状态类。
2. 多了一个I_State成员变量,表示当前的状态,同时,表示状态的枚举变量删掉了,因为已经有了状态类。
那么,我们之前的逻辑几乎没有什么变化,依旧是在木头的update函数里做文章,只不过,这次的update函数可就简单多了~~~
(旁白:真的吗?倒是有点兴趣~)
#include "MutouT.h" #include "I_State.h" bool MutouT::init() { mCurState = NULL; this->scheduleUpdate(); return true; } bool MutouT::isTire() { /* 每次问木头累不累,他都会说:累~ */ return true; } bool MutouT::isWantToWriteArticle() { /* 有10%的概率想写教程(好懒~!) */ float ran = CCRANDOM_0_1(); if(ran < 0.1f) { return true; } return false; } void MutouT::writeCode() { CCLOG("mutou is wirting Code."); } void MutouT::writeArticle() { CCLOG("mutou is writing article."); } void MutouT::rest() { CCLOG("mutou is resting."); } void MutouT::changeState( I_State* state ) { CC_SAFE_DELETE(mCurState); mCurState = state; } void MutouT::update( float dt ) { mCurState->execute(this); }
前面几乎没有变,看看最后那个:
void MutouT::update( float dt ) {
mCurState->execute(this);
}
这个就是新的update函数的处理了,是不是很简单?
先看看效果再解释,修改HelloWorld的init函数:
bool HelloWorld::init() { bool bRet = false; do { CC_BREAK_IF(! CCLayer::init()); /* 新建木头2角色 */ mMutou = MutouT::create(); /* 初始化木头的状态为休息 */ mMutou->changeState(new StateRest()); this->addChild(mMutou); bRet = true; } while (0); return bRet; }
和以前差不多,只是把Mutou类换成新的MutouT类。
(旁白:MutouT?就是MutouTow的意思?也就是Mutou2 ?噗,是的,木头是挺2的~)
然后用调试模式运行项目,将看到以下输出:
mutou is wirting Code.
mutou is resting.
mutou is writing article.
mutou is resting.
mutou is writing article.
mutou is resting.
mutou is wirting Code.
mutou is resting.
mutou is wirting Code.
mutou is resting.
mutou is wirting Code.
OK,和以前一样的效果。
稍微解释一下,其实这就是简单地利用了多态。神奇的地方就在execute这个函数,这个函数里面会根据不同的情况切换MutouT的当前状态,比如写教程状态的execute函数:
void StateWriteArticle::execute( MutouT* mutou ) { /* 如果累了就休息,并且切换到休息状态 */ if(mutou->isTire()) { mutou->rest(); mutou->changeState(new StateRest()); } }
如果累了,就切换到休息状态,那么,执行完这个函数之后,MutouT的状态其实就已经改变了,变成了休息状态,那么下一次的update函数就会调用休息状态的execute函数。以此类推。
看一个图,会不会好理解一些?
好吧,我承认我解释得有点糟糕,希望大家能看明白...
(旁白:我已经晕了。。。)