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方法裡面來做碰撞檢測等物理操作。
GameModel實現的update方法,不僅僅用來更新自己的狀態,同時,它還呼叫player的update方法和所有platform的update方法。這個update方法,之後會被game loop所呼叫。// 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.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有更好的看法,歡迎補充。