Bartek Wilczynski bio photo

Bartek Wilczynski

IT consultant and entrepreneur with over 10 years of experience in a variety of medium-size line of business applications, mostly in .NET technology.

Email Twitter LinkedIn Youtube

My last post about implementing MVC pattern in cocos2d was definitely lacking some code examples. I must admit – I was writing it on my PC notebook when I was on a business trip and I didn’t have an access to my game source code. This is a follow-up post to the previous one, so I suggest that you read it before this one.

Model classes

As described previously GameModel class stores information about game world properties, such as current gravity but it also is responsible for building and coordinating all other game models, such as Player and Platforms.

MVC in cocos2d - model

You may notice that all of my model classes implement update method from Updateable protocol. This is to ensure that all of my model class can update its status accordingly during game loop. For example in Player class I need to update player’s position based on its current velocity along x and y axes. In my example I delegate it to my Physics component which is a simple physics engine implementation of my own, but if your game is simpler and you don’t need to separate your physics code you can also do necessary calculations directly in your update method.

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

GameModel implementation of update method not only updates its own state, but it also needs to delegate update call to Player and Platforms. This method will be later on called by Controller 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];
    }
    // ...
}

View and controller classes

For each of my game scene I have a separate Controller class which is responsible for handling user interactions, creating views and managing screen flow. Controller also schedules a main game loop from which the model and view is updated.

@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 main responsibility is to render a model on screen but also, because of the way cocos2d is built, we need to also delegate touch events to Controller class from CCLayer classes. You should notice that view doesn't directly depend on a Controller. View class calls controller methods via GameViewDelegate protocol that controller implements, that's why we need to pass it as a parameter to init method.

@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;
}

// EDIT: I realized I forgot to mention (thanks Zenkimoto) how the implementation of a layer itself look like
In a layer itself I pretty much do the standard stuff, like creating the sprites, actions, animations etc.

@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
}

Sprites on layers are updated in update method that all layers implement. In this method sprites properties are updated from the model in the following way:

- (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);
}

Notice that in order to render sprite position I am using PPM_RATIO that I use to convert meters to points.
// end EDIT
Touch events are delegated to controller class via layer's delegate as following:

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

And here is a complete picture of view and controller classes in a sample class diagram.

MVC in cocos2d - controllers

Handling model events

Open question from my last post was how to handle communication between model and controller classes. The answer is in dotted arrow between model and controller in MVC diagram - we can publish events from Model to which controller subscribes and responds accordingly. For example in Player model we can raise an event that player has just begun jumping:

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

Controller observes the event, and when it happens it calls appropriate event handler to play sound.

@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];
}

That's it

It might look a little bit complicated at first and you may be a bit scared with all of those classes that you need to add in order to make it work. But you need to remember that the belief that adding many classes makes the design complicated is a well known anti-pattern known as Fear of Adding Classes so if you are not such a believer I suggest you give this pattern a try - from my experience separating concerns and making different components responsible only for single thing is a principle that makes your design much better and pays off in future.