cocos2d里面如何實現MVC(二)


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

模型類

    就像之前所討論的,GameModel類存儲了游戲世界里面的一些屬性,比如當前的重力。但是,它同時也負責創建和連接游戲里面的對象,比如Player和Platforms。它們之間的關系如下圖所示:(譯者:這里采用了針對接口編程的方法,所有的游戲對象都繼承至updateable接口,這樣就可以在game loop里面更新自己了。同時GameModel類提供了一個工廠方法createGameObjects,用來創建游戲里面的對象。)

 

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

 

@implementation Player
- (void)update:(ccTime)dt
{
[_physics updateModel:self dt:dt];
// detect collisions with game objects, etc.
}

GameModel實現的update方法,不僅僅用來更新自己的狀態,同時,它還調用player的update方法和所有platform的update方法。這個update方法,之后會被game loop所調用。

@implementation GameModel
- (void)update:(ccTime)dt
{
// modify game model properties here
// update player
[self.player update:dt];
// update platforms
for (Platform *platform in _platforms) {
[platform update:dt];
}
// ...
}

視圖和控制器類

    對於我的游戲里面的每一個場景(CCScene),都關聯了一個Controller類,它負責處理用戶交互、創建視圖和管理場景的跳轉。控制器會schedule一個游戲主循環,在這個loop里面,所有的model和view的update方法都會被調用。

@implementation GameplayController
- (id)init
{
if((self=[super init])) {
GameplayView *view = [[GameplayView alloc] initWithDelegate:self];
// retain view in controller
self.view = view;
// release view
[view release];

// init model
GameModel *model = [GameModel sharedModel];
[model createGameObjects];
[model.player run];

[self scheduleUpdate];
}
}

- (void)update:(ccTime) dt
{
GameModel *model = [GameModel sharedModel];

if (model.isGameOver) {
[[CCDirector sharedDirector] replaceScene:[GameOverController node]];
}

// process model
[model update:dt];

// update view
[self.view update:dt];
}

View主要負責根據model的狀態來渲染游戲畫面。但是,同時,因為cococs2d的實現方式,我們還需要把touch事件傳遞給controller類。你應該注意到了,view不並直接依賴controller。view類調用controller的方法是通過GameViewDelegate協議來實現的。這也是為什么我們要在init方法里面傳遞一個delegate的原因。 

@implementation GameplayView
- (id)initWithDelegate:(id)theDelegate
{
if ((self = [super init])) {
self.delegate = theDelegate;

// initialize layers
_backgroundLayer = [GameplayBackgroundLayer node];
[self.delegate addChild: _backgroundLayer];

_platformLayer = [GameplayPlatformLayer node];
[self.delegate addChild:_platformLayer];

_playerLayer = [GameplayPlayerLayer node];
_playerLayer.delegate = theDelegate;
[self.delegate addChild: _playerLayer];

_hudLayer = [GameplayHudLayer node];
_hudLayer.delegate = theDelegate;
[self.delegate addChild:_hudLayer];
}

return self;
}

// 更新:我忘了告訴大家layer本身是怎么實現的了。其實很簡單,就是創建一些sprite、action和animation等。 

@implementation GameplayPlayerLayer
- (id)init
{
if ((self = [super init])) {
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
ResourceManager *resources = [ResourceManager sharedResourceManager];

[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:PLAYER_SPRITE_SHEET_PLIST];

CCSpriteBatchNode *spriteSheet = [resources playerSpriteSheet];
[self addChild:spriteSheet];
// ...
// initialize sprites
// initialize animations
}

    層里面的精靈都會在layer的update方法里面被更新,如下所示:

 

- (void)update:(ccTime)dt 
{
// update player sprite based on model
GameModel *model = [GameModel sharedModel];

_playerSprite.position = ccp((model.player.position.x - model.viewPort.rect.origin.x) * PPM_RATIO, (model.player.position.y - model.viewPort.rect.origin.y) * PPM_RATIO);
}

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

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

@implementation GameplayPlayerLayer
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.delegate playerBeginJump];
}

  然后下圖就是view和controller交互的完整的UML圖:

處理模型事件

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

@implementation Player
- (void)beginJump
{
if ([_gameModel isOnGround:self]) {
[[NSNotificationCenter defaultCenter] postNotificationName:EVENT_PLAYER_BEGIN_JUMP object:nil];
...
}

   controller訂閱事件,當事件發生的時候會得到通知,同時相應的事件處理函數將會被調用。

@implementation GameplayController
- (id)init
{
if ((self = [super init])) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPlayerBeginJumpNotification:) name:EVENT_PLAYER_BEGIN_JUMP object:nil];
...
}
}

- (void)onPlayerBeginJumpNotification:(NSNotification *)notification
{
[[SimpleAudioEngine sharedEngine] playEffect:PLAYER_JUMP_SOUND];
}

就這么多!

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





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM