在上一篇文章中,我們使用cocos2d基於mvc做了一個簡單了游戲架子,這個架子還非常簡單,還有許多東西有待實現。
介紹模型
在上一篇博文中,我們介紹了view和controller。為了實現mvc模式,我們還需要添加一個model類來維護游戲的狀態。我們的實現應該要包含下列這些類:
- GameBoardView - 也就是View,
- GameBoardController - 也就是Controller.
- GameBoard – 也就是Model.

Model 實現
GameBoard 實現
我們在第一部分所描述的需求是這樣子的:
。。。一個game board是通過n行n列組成的,它會隨着游戲難度有所變化。
因此,我們按照下面的編碼方式來實現之:
@interface GameBoard : NSObject {
NSInteger numberOfRows;
NSInteger numberOfColumns;
}
- (id)initWithRows:(NSInteger)aNumberOfRows columns:(NSInteger)aNumberOfColumns;
@property (nonatomic) NSInteger numberOfRows;
@property (nonatomic) NSInteger numberOfColumns;
@end
請注意,model是從NSObject繼承過來的---因為model只需要關注game board的狀態就行了(當然,還有相應的更新狀態的方法)---我們不應該把其它東西也放進來,比如繼承到CCNode就不行,我們並不需要CCNode的東西,所以,為了純粹性,我們這里繼承到game board。
GameBoardView 的實現
我們現在需要修改View,同時它包含一個model的引用,我們可以通過initWithGameBoard方法來初始化這個成員變量:
@interface GameBoardView : CCNode {
GameBoard *gameBoard;
}
@property (nonatomic, retain) GameBoard *gameBoard;
- (id)initWithGameBoard:(GameBoard *)aGameBoard;
@end
具體GameBoardView的實現細節如下:(為了演示方便,我們忽略了實際渲染每一個小方塊的代碼)
- (id)initWithGameBoard:(GameBoard *)aGameBoard {
if ((self = [super init])) {
// retain gameboard
self.gameBoard = aGameBoard;
// render gameboard background
CCSprite *gameboardSprite = [CCSprite spriteWithFile:@"gameboard.png"];
gameboardSprite.anchorPoint = CGPointMake(0, 0);
[self addChild:gameboardSprite];
// render spaces
for (int i = 0; i < gameBoard.numberOfRows; i++) {
for (int j = 0; j < gameBoard.numberOfColumns; j++) {
// position and render game board spaces
}
}
}
return self;
}
GameBoardController
最后,我們要更新GameBoardController的init方法,因為view需要把GameBoard對象通過init方法注入進去,所以,我們在controller的init方法里面,就應該定義好model對象,然后傳遞給view。
- (id)init {
if ((self = [super init])) {
// initialize model
gameBoard = [[GameBoard alloc] initWithRows:7 columns:9];
// initialize view
view = [[GameBoardView alloc] initWithGameBoard:gameBoard];
[self addChild:view];
}
return self;
}
處理touch事件
GameBoardView updates
為了能夠處理touch事件,我們需要再稍微修改一下View。我們讓它繼承至CCLayer,而不是CCNode。因為CCLayer內置了處理touch事件的方法:
@interface GameBoardView : CCLayer {
...
}
而view本身是不應該處理用戶的交互(touch事件)的,所以,我們需要定義一個代理(GameBoardViewDelegate)。(譯者:為什么這里要定義代理呢?所謂代理代理,當然就是你不想做的事,找別人去做,這就是代理。所以,當你寫代碼的時候,你想保持類的簡單性、重用性,你就可以把事件盡量都交給其它類去做,自己只管做好自己的事。也就是SRP,單一職責原則。如果一個類關注的點過多,做的事情太多。這些事情不管是你直接做的,還是調用別的對象去完成的。這都不行,自己做這些事,那就會使類的功能復雜化,維護不方便。而過多地調用其它對象來完成一些事情,表面上看起來好像不錯,實際上是過度耦合了。我們編寫類的原則應該是追求高內聚,低耦合的。可能你會說,用代理不也是交給別人做嗎?沒錯,問的好。但是,代理是接口,我們是針對接口編程,所以它的重用性會非常好。因此,下次當你想寫可擴展和可重用的代碼的時候,不妨先想想代理這個東西吧。objc里面delegate是用protocol實現的,而java和c++則是用接口實現的,具體他們之間怎么轉換的,比較一下應該就可以了。)
@protocol GameBoardViewDelegate
- (void)gameBoard:(GameBoard *)gameBoard touchedAtRow:(int)row column:(int)column;
@end
我們還需要再修改一下GameBoardView的init方法,通過傳送一個delegate進來處理touch事件。
- (id)initWithGameBoard:(GameBoard *)aGameBoard delegate:(id)aDelegate;
下面是touch事件的具體實現:
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [self convertTouchToNodeSpace:touch];
// calculate row and column touched by the user and call a delegate method
// ...
[self.delegate gameBoard:self.gameBoard touchedAtRow:row column:column];
}
GameBoardController 更新
GameBoardController將會負責處理用戶touch事件,所以,我們需要讓GameBoardController實現GameBoardViewDelegate接口:
@interface GameBoardController : CCNode<GameBoardViewDelegate>
- (void)gameBoard:(GameBoard *)gameBoard touchedAtRow:(int)row column:(int)column {
// do the game logic here and update view accordingly
}
還有最后一步,那就是修改view的init方法,把controller傳遞進去。
// initialize view
view = [[GameBoardView alloc] initWithGameBoard:gameBoard delegate:self];
總結
在這篇文章中,我們實現了Model,同時還通過一些代碼把view和controller聯系起來了。同時,我們還在view和controller,以及用戶touch事件之間建立了聯系,這樣controller類就可以來響應游戲里面的用戶輸入了。(譯者:為什么費這么多勁,無非就是職責分離,這個非常重要!)。在接下來的文章里面,我們會談到下面兩個問題:
- 在Controller里面更新Model,
- 通知View關於Model的改變.
后記:本文已同步更新到cocos2d mvc這個系列里面去了。
如果你覺得本文章對你有所幫助,請您點一下旁邊的“推薦”按鈕,這樣可以讓更多的人看到,同時也會給我寫作的動力,謝謝大家。
