Cocos2d-x裡面如何實現MVC(四)
在上一篇文章中,我們使用cocos2d-x基於mvc做了一個簡單了遊戲架子,這個架子還非常簡單,還有許多東西有待實現。
介紹模型
在上一篇博文中,我們介紹了view和controller。為了實現mvc模式,我們還需要新增一個model類來維護遊戲的狀態。我們的實現應該要包含下列這些類:
1 GameBoardView - 也就是View,
2 GameBoardController - 也就是Controller.
3 GameBoard – 也就是Model.
Model 實現
GameBoard 實現
我們在第一部分所描述的需求是這樣子的:
。。。一個game board 是通過n行n列組成的,它會隨著遊戲難度有所變化。
因此,我們按照下面的編碼方式來實現之:
Class GameBoard : public CCObject { public: int numberOfRows; int numberOfColums; void initGridSize(int aNumberOfRows, int aNumberOfColumns){ this->numberOfColumns = aNumberOfColumns; this->numberOfRows = aNumberOfRows; } }
請注意,model是從CCbject繼承過來的---因為model只需要關注game board model的狀態就行了(當然,還有相應的更新狀態的方法)---我們不應該把其它東西也放進來,比如繼承到CCNode就不行,我們並不需要CCNode的東西,所以,為了純粹性,我們這裡繼承到game board 。
GameBoardView 的實現
我們現在需要修改View,同時它包含一個model的引用,我們可以通過initWithGameBoard方法來初始化這個成員變數:
具體GameBoardView的實現細節如下:(為了演示方便,我們忽略了實際渲染每一個小方塊的程式碼)class GameBoardView :: public CCLayer { public: GameBoard *gameBoard; void initWithGameBoardModel(GameBoard *aGameBoard); }
void GameBoardView::initWithGameBoard(GameBoard *aGameBoard)
{
if(gameBoard != NULL){
gameBoard->release();
gameBoard = NULL;
}
this->gameBoard = aGameBoard;
this->gameBoard->retain();
// render gameBoard background
CCSprite *gameBoardSprite = CCSprite::spriteWithFile("gameBoard.png");
gameBoardSprite->setAnchorPoint(ccp(0, 0));
this->addChild(gameBoardSprite);
for(int i=0; i<gameBoard->numberOfRows; i++)
for (int j=0; j<gameBoard->numberOfColumns; j++)
{
// position and render game board spacse
}
}
GameBoardController
最後,我們要更新GameBoardController的init方法,因為view需要把GameBoard物件通過init方法注入進去,所以,我們在controller的init方法裡面,就應該定義好model物件,然後傳遞給view。
virtual bool init(){
bool bRet = false;
do{
// 先呼叫超類的init方法
CC_BREAK_IF(! CCLayer::init());
GameBoard *gameBoard = new GameBoard();
gameBoard->initGridSize(7, 9);
view = GameBoardView::node();
view->initWithGameBoard(gameBoard);
this->addChild(view, 0);
bRet = true;
}while(0)
beturn bRet;
}
處理touch事件
GameBoardView updates
我們的View雖然繼承了CCLayer,但view本身是不應該處理使用者的互動(touch事件)的,所以,我們需要定義一個代理(GameBoardViewDelegate)。(譯者:為什麼這裡要定義代理呢?所謂代理代理,當然就是你不想做的事,找別人去做,這就是代理。所以,當你寫程式碼的時候,你想保持類的簡單性、重用性,你就可以把事件儘量都交給其它類去做,自己只管做好自己的事。也就是SRP,單一職責原則。如果一個類關注的點過多,做的事情太多。這些事情不管是你直接做的,還是呼叫別的物件去完成的。這都不行,自己做這些事,那就會使類的功能複雜化,維護不方便。而過多地呼叫其它物件來完成一些事情,表面上看起來好像不錯,實際上是過度耦合了。我們編寫類的原則應該是追求高內聚,低耦合的。可能你會說,用代理不也是交給別人做嗎?沒錯,問的好。但是,代理是介面,我們是針對介面程式設計,所以它的重用性會非常好。因此,下次當你想寫可擴充套件和可重用的程式碼的時候,不妨先想想代理這個東西吧。C++和java者是用介面實現的,而objc裡面delegate是用protocol實現的,具體怎麼轉換的,比較一下應該就可以了。)
class GameBoardViewDelegate
{
public:
virtual void dealWithTouchesBegan(GameBoard *gameBoard, int row, int column) = 0;
};
我們還需要再修改一下GameBoardView的init方法,通過傳送一個delegate進來處理touch事件。
void initWithGameBoard(GameBoard *aGameBoard, GameBoardViewDelegate *aDelegate);
下面是touch事件的具體實現,記得先在初始化時setIsTouchEnable(true)哦^_^:
void GameBoardView::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
CCTouch *touch = (CCTouch*) pTouches->anyObject();
if(touch == NULL)
return;
// 置換座標,等效如下
//CCPoint location = touch->locationInView(touch->view());
//location = CCDirector::sharedDirector()->convertToGL(location);
CCPoint point = this->convertTouchToNodeSpace(touch);
// calculate row and column touched by the user and call a delegate method
int row = 0;
int column = 0;
// ...
this->gameBoardViewDelegate->dealWithTouchesBegan(gameBoard, row, column);
}
GameBoardController 更新
GameBoardController將會負責處理使用者touch事件,所以,我們需要讓GameBoardController實現GameBoardViewDelegate介面:
class GameBoardController :
public CCLayer, public GameBoardViewDelegate
{
public:
//...
virtual void dealWithTouchesBegan(GameBoard *gameBoard, int row, int column){
// do the game logic here and update view accordingly
}
// ...
};
還有最後一步,那就是修改view的init方法,把controller傳遞進去。
// initialize view
view->initWithGameBoard(gameBoard, this);
總結
在這篇文章中,我們實現了Model,同時還通過一些程式碼把view和controller聯絡起來了。同時,我們還在view和controller,以及使用者touch事件之間建立了聯絡,這樣controller類就可以來響應遊戲裡面的使用者輸入了。(譯者:為什麼費這麼多勁,無非就是職責分離,這個非常重要!)。在接下來的文章裡面,我們會談到下面兩個問題:
· 在Controller裡面更新Model,
· 通知View關於Model的改變.