天天看點

【木頭Cocos2d-x 023】狀态機篇(第02章) --狀态模式之我很胖但我很強!【Cocos2d-x 狀态機篇】第02章--狀态模式之我很胖但我很強!

【Cocos2d-x 狀态機篇】第02章--狀态模式之我很胖但我很強!

笨木頭花心貢獻,啥?花心?不呢,是用心~

轉載請注明,原文位址: http://blog.csdn.net/musicvs/article/details/8348323

正文:

狀态機的應用當然就少不了狀态模式了,因為它們都有“狀态”兩個字。

(旁白:總感覺這句話十分不可靠= =)

本章我們來簡單地介紹一下狀态模式,并且利用狀态模式優化我們的有限狀态機的實作。

1. 什麼是狀态模式

說實話,我不知道,但我知道怎麼使用...

用一句話來概括,也許是這樣:同一件事情,讓不同的人去做,每個人都會用自己的方式來做事,但,同一時間,隻會讓一個人在做這件事情。由老闆來決定某一刻由誰來做事。

(旁白:又你可愛的妹紙的,這明明是兩句話,兩個句話有沒有= =)

用程式來說,也許是這樣:同一個動作,讓不同的類去執行,每個類都有自己獨特的處理方式,至于由哪個類來執行這個動作,要根據目前的狀态來判斷。并且,不同的類執行完動作之後,會切換狀态。

(旁白:還是沒聽懂。。。)

OK,我相信大家都明白了什麼是狀态模式了,接下來我們把第01章的例子稍微改改,很簡單的~!

(旁白:等等,我壓根兒就沒搞懂什麼是狀态模式啊!)

好吧,我畫了一個圖,幫助大家了解:

【木頭Cocos2d-x 023】狀态機篇(第02章) --狀态模式之我很胖但我很強!【Cocos2d-x 狀态機篇】第02章--狀态模式之我很胖但我很強!

圖中有一個對象(藍色方塊),有三個狀态處理類。

比如我有一件事情要做,那就是,起床之後要做的事情,但是由哪個類來執行這件事情呢?這要根據我的目前狀态來決定(我有三種狀态:肚子餓,想看電影,想吐槽),預設的狀态是肚子餓。

然後,今天起床,我的狀态是“肚子餓”。那我就會自動調用小木廚師來執行事件,執行完之後,狀态會切換到“想看電影”。

那麼,第二天,我起床的狀态就是“想看電影”。那我就會自動調用電影院來執行事件,執行完之後,狀态會切換到“想吐槽”。

第三天,我起床的狀态就是“想吐槽”。那我就會自動調用吐槽旁白來執行事件,執行完之後,狀态會切換到“肚子餓”。

第四天,我起床之後的狀态就是“肚子餓”。那我就會自動調用小木廚師來執行事件,執行完之後,狀态切換到“想看電影”。

第五天,我起床...(旁白:停~!!!你想說到天亮嗎?!)

其實這個例子有點不準确,甚至是十分不準确,但是對于了解狀态模式還是有點幫助了。

現在,大家在接着往下看之前,請确認你已經大概了解狀态模式(起碼去百度過,看過一篇正式介紹狀态模式的文章)。因為接下來的代碼和上面那張圖的差别還是不小的,如果對狀态模式不了解,就會産生混亂。

那麼,用什麼方法實作自動調用哪個類呢?當然是多态了,小木廚師、電影院、吐槽旁白這三個類都繼承了同一個父類,它們就是我們要說的狀态類了。

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函數。以此類推。

看一個圖,會不會好了解一些?

【木頭Cocos2d-x 023】狀态機篇(第02章) --狀态模式之我很胖但我很強!【Cocos2d-x 狀态機篇】第02章--狀态模式之我很胖但我很強!

好吧,我承認我解釋得有點糟糕,希望大家能看明白...

(旁白:我已經暈了。。。)

繼續閱讀