1. 程式人生 > >Cocos2d-x裡面如何實現MVC(二)

Cocos2d-x裡面如何實現MVC(二)

   上一篇博文中,我提到了《如何在cocos2d-x裡面實現mvc(一)》,但是,都是一些純理論的東西,我們需要看一些程式碼才能理解地更清楚。這篇博文是基於上一篇來寫的,所以我建議你先閱讀完上一篇。

模型類

    就像之前所討論的,GameModel類儲存了遊戲世界裡面的一些屬性,比如當前的重力。但是,它同時也負責建立和聯接遊戲裡面的物件,比如Player和Platforms。它們之間的關係如下圖所示:(譯者:這裡採用了針對介面程式設計的方法,所有的遊戲物件都例項updateable介面,這樣就可以在game loop裡面更新自己了。同時GameModel類提供了一個工廠方法createGameObjects,用來建立遊戲裡面的物件。)


    你可能已經注意到了,所有的model類都實現了updateable protocol,並實現了update方法。這樣它們就可以在game loop裡面更新自己的狀態了。比如,在Player類裡面,我們需要根據當前x軸和y軸的速度來更新player的位置資訊。在我的遊戲裡面,我把它委託給Physics元件,它是我實現的一個簡單的物理引擎。但是,假如你的遊戲很簡單的話,你可以不用分開你的物理程式碼,然後可以直接在update方法裡面來做碰撞檢測等物理操作。

// Player.h
 
#include "cocos2d.h"
using namespace cocos2d;
 
class Player : public Updateable{
public:
    void update(ccTime dt){
        _physics->updateModel(this, dt);
        // detect collisions with game objects, etc.
    }
}
GameModel實現的update方法,不僅僅用來更新自己的狀態,同時,它還呼叫player的update方法和所有platform的update方法。這個update方法,之後會被game loop所呼叫。
// GameModel.h
 
#include "cocos2d.h"
using namespace cocos2d;
 
class GameModel : public Updateable{
public:
    virtual void update(ccTime dt){
        // modify game model properties here   
        // update player
        this->player->update(dt);
        // update platforms 
        for(int i=0; i<_platforms.size(); i++){
            _platforms.at(i)->update(dt);
        }
        // ...
    }
}

檢視和控制器類

    對於我的遊戲裡面的每一個場景,都關聯了一個Controller類,它負責處理使用者互動、建立檢視和管理場景的跳轉。控制器會schedule一個遊戲主迴圈,在這個loop裡面,所有的model和view的update方法都會被呼叫。

// GameplayController.h
 
#include "cocos2d.h"
using namespace cocos2d;
 
class GameplayController : public GameplayViewDelegate{
public:
    virtual bool init() {
        GameplayView *view = new GameplayView();
        view->initWithDelegate(this);
        // retain view in controller
        this->view = view;
        // release view
        view->release();
        // init model
        GameModel *model = GameModel::sharedModel();
        model->createGameObjects();
        model->getPlayer()->run();
        this->scheduleUpdate();
 
        return true;
    }
     
    void update(ccTime dt) {
        GameModel *model = GameModel::shareMode();
        if(model->getIsGameover()) {
            CCDirector::sharedDirector()->replaceScene(GameOverController::node());
            // process model
            model->update(dt);
            // update view
            this->view->update(dt);
        }
    }
}
View主要負責根據model的狀態來渲染遊戲畫面。但是,同時,因為cococs2d的實現方式,我們還需要把touch事件傳遞給controller類。你應該注意到了,view不併直接依賴controller。view類呼叫controller的方法是通過GameViewDelegate協議來實現的。這也是為什麼我們要在init方法裡面傳遞一個delegate的原因。
// GameplayView.h
 
#include "cocos2d.h"
using namespace cocos2d;
 
class GameplayView {
public:
    void initWithDelegate(CCObject *theDelegate) {
        this->delegate = theDelegate;
     
        // initialize layers 
        _backgroundLayer = GameplayBackgroundLayer::node();
        this->delegate->addChild(_backgroundLayer);
     
        _platformLayer = GameplayPlatformLayer::node(); 
        this->delegate->addChild(_platformLayer);
     
        _playerLayer = GameplayPlayerLayer::node();
        _playerLayer.delegate = theDelegate;
        this->delegate->addChild(_playerLayer);
     
        _hudLayer = GameplayHudLayer::node();
        _hudLayer.delegate = theDelegate;
        this->delegate->addChild(_hudLayer);
    }
}
更新:我忘了告訴大家layer本身是怎麼實現的了。其實很簡單,就是建立一些sprite、action和animation等。
// GameplayPlayerLayer.h
 
#include "cocos2d.h"
using namespace cocos2d;
 
class GameplayPlayerLayer : public CCLayer, public Updateable{
public:
    virtual bool init() {
        this->setIsTouchEnabled(true);
        this->setIsAccelerometerEnabled(true);
        // ResourceManager is the self-declared class
        ResourceManager *resources = ResourceManager::sharedResourceManager();
        CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(PLAYER_SPRITE_SHEET_PLIST);
        CCSpriteBatchNode *spriteSheet = resources->playerSpriteSheet;
        this->addChild(spriteSheet);
        // ...      
        // initialize sprites     
        // initialize animations
    }
    // ...
}
    層裡面的精靈都會在layer的update方法裡面被更新,如下所示:
void update(ccTime dt)
{
    // update player sprite based on model
    GameModel *model = GameModel::sharedModel();
     
    _playerSprite->setPosition(ccp((model->getPlayer()->getPosition().x - model->getViewPort()->getRect()->getOrigin().x)*PRM_RATIO, model->getPlayer()->getPosition().h - model->getViewPort()->getRect()->getOrigin().y)*PRM_RATIO);
}

    注意,在渲染player的位置的時候,我們使用了PPM_RATIO,用來把米轉換成point。(為什麼是point而不是pixel,因為cocos2d使用的是point而不是pixel,不明白的可以看看原始碼和官方文件)

    touch事件被傳遞給了controller類,如下所示:

class GameplayPlayerLayer : public CCLayer, public Updateable{
    // ...
    virtual void ccTouchesBegan(CCset *pTouches, CCEvent *pEvent){
        this.delegate->playerBeginJump();
    }
}
  然後下圖就是view和controller互動的完整的UML圖:

處理模型事件

    上一篇博文中,我留下了一個問題,就是怎麼處理model和controller之間的互動。其它很簡單,就是使用觀察者模式,controller只要訂閱model的事件,然後定義相應的處理方法即可。當model更新的時候,會觸發事件,然後所以偵聽了該事件的controller都能被通知到。下面給出實現:(譯者:很多童靯不知道物件之間該怎麼互動,其實使用Notification可以大大地解耦物件的互動,使程式碼更容易維護。)

class Player : public Updateable{
public:
    void beginJump(){
        // 在Object-c中有NSNotificationCenter類,在C++中要自行實現該類
        NotificationCenter::shareCenter()->postNotificationName(EVENT_PLAYER_BEGIN_JUMP, NULL);
    }
}
   controller訂閱事件,當事件發生的時候會得到通知,同時相應的事件處理函式將會被呼叫。
class GameplayController : public GameplayViewDelegate{
public:
    virtual bool init() {
        NotificationCenter::shareCenter()->addObserver(this, callfunc_selector(GameplayController::onPlayerBeginJumpNotification), EVENT_PLAYER_BEGIN_JUMP, NULL);
 
    //...
    }
     
    void onPlayerBeginJumpNotification(){
        SimpleAudioEngine::sharedEngine()->playEffect(PLAYER_JUMP_SOUND);
    }
}

就這麼多!

    乍一看,可能會覺得有點複雜。而且,要建立這麼多類,而只是為了實現一個簡單的功能,確實有點划不來。而且,你還記得嗎?如果在系統裡面新增太多的類,其實是一種反模式(anti-pattern),叫做Fear of Adding Classes。但是,從長遠的角度來看,從可維護性的角度來看,加這麼多類是值得的。後面的教程我將向大家展示出來。如果大家對於如何在cocos2d裡面使用mvc有更好的看法,歡迎補充。