【木头Cocos2d-x 023】状态机篇(第02章) --状态模式之我很胖但我很强!

Cocos2d-x状态机篇】第02--状态模式之我很胖但我很强!

笨木头花心贡献,啥?花心?不呢,是用心~

转载请注明,原文地址
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);
}


前面几乎没有变,看看最后那个:

voidMutouT::update(floatdt){

mCurState->execute(this);

}

这个就是新的update函数的处理了,是不是很简单?

先看看效果再解释,修改HelloWorldinit函数:

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~

然后用调试模式运行项目,将看到以下输出:

mutouiswirtingCode.

mutouisresting.

mutouiswritingarticle.

mutouisresting.

mutouiswritingarticle.

mutouisresting.

mutouiswirtingCode.

mutouisresting.

mutouiswirtingCode.

mutouisresting.

mutouiswirtingCode.

OK,和以前一样的效果。

稍微解释一下,其实这就是简单地利用了多态。神奇的地方就在execute这个函数,这个函数里面会根据不同的情况切换MutouT的当前状态,比如写教程状态的execute函数:

void StateWriteArticle::execute( MutouT* mutou ) {
    /* 如果累了就休息,并且切换到休息状态 */
    if(mutou->isTire()) {
        mutou->rest();
        mutou->changeState(new StateRest());
    }
}


如果累了,就切换到休息状态,那么,执行完这个函数之后,MutouT的状态其实就已经改变了,变成了休息状态,那么下一次的update函数就会调用休息状态的execute函数。以此类推。

看一个图,会不会好理解一些?

好吧,我承认我解释得有点糟糕,希望大家能看明白...

(旁白:我已经晕了。。。)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值