Cocos2d Tips: Connect Layers

Games have many layers.

layers

The most common example is the main action layer (where the players and enemies are) having to communicate with the HUD layer (which presents score, health and other info) to the gamer. Let’s see how we can communicate between layers.

Typically you create a Scene and then a Layer in order to add that Layer as a child to the Scene.

Let’s say our Scene.h looks like this:

#import <Foundation/Foundation.h>

#import “cocos2d.h”

@interface Scene1 : CCScene {

}

@end

And our Scene.m looks like this:

-(id)init {

if ((self = [super init])) {

Scene1ActionLayer * actionLayer = [[[Scene1ActionLayer alloc] init] autorelease];

[self addChild:actionLayer z:0 tag:kActionLayer];

}

return self;

}

So we are adding the main action layer, actionLayer, to our scene at z=0 with a tag kActionLayer.  Simple enough.  Now let’s say we want to create a HUD layer to contain game information.  This can range from points, to level information, to instructions or simply health or powerup information.

Im not much of a designed so I created this simple rectangle as a bottom bar for my game.

HUDbar

The idea here is that those stars will actually represent the powerups a player must collect in order to complete a level.  Thus this will actually be a silhouette and each time the player picks up a power up, the HUD gets updated to place the powerup image in that space.  So we need the actionLayer to communicate with the HUD layer in order to say “Update the BIG GUNS power up!”.

We go ahead and create the actionLayer, possibly with a method called updateHUDWithPowerup:(Powerup*)power up; where Powerup is anything as simple as a typedef or as complex as a custom NSObject.

First, we need an instance of the HUD layer inside our actionLayer to call its method.  So inside our actionLayer, we forward declare it in our action layer :

@class “HUDLayer;

and then create an ivar for its instance:

HUDLayer *hudLayerInstance;

Second, we import the class in our action layer.m:

#import “HUDLayer.h”

Third, we simply allocate the instance in our actionLayer’s init method:

hudLayerInstance = [[HUDLayer alloc] initWithActionLayer:self];

[self addChild:backLayer z:0 tag:100];

Fourth, when the time comes to update the HUDLayer from the action layer’s update method (perhaps), we call:

[hudLayerInstance someMethodDefinedInHUDLayer:withThisValue];

Fifth, don’t forget to code the HUDLayer.  In its interface, forward declare your action layer class and add the custom initializer as well as your method like so:

@class ActionLayer;

@interface HUDLayer : CCLayer {

CCLabelTTF *_scoreLabel;

}

@property (nonatomic, assign) CCLabelTTF *scoreLabel;

-(id)initWithActionLayer:(ActionLayer*)layer;

@end

Finally implement your initializer and its “someMethodDefinedInHUDLayer” in order to pass it “withThisValue”.

Voila!  Another option is using Delegates/Protocols.

 

 

In this case we are adding all children to the Scene.  In order to get them to communicate, we would need to have methods inside the Scene itself to call actions between objects.  I prefer the former method.  I leave this kind of setup for adding layers that don’t really do much, such as timers, background, eye candy etc.  They are still important layers, they just dont interact with the actionLayer much or at all.

NSOperationQueue & NSInvocationOperation

1. In your main class’ init method call this:

operationQueue = [[NSOperationQueue alloc]init];
[operationQueue setMaxConcurrentOperationCount:1];

2. Then in your viewWillAppear you can call this:

[self showLoadingIndicators]; //calls a method which presents loading indicators (optional)
[self beginLoadingTwitterData]; // this is the method that fires it all off

3. In your beginLoadingTwitterData you call this:

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(synchronousLoadTwitterData) object:nil];
[operationQueue addOperation:operation]; // creates and adds NSOperation to a queue
[operation release];

4. In this synchronousLoadTwitterData is where you do the heavy lifting:

NSDictionary *dict = [[NSDictionary alloc] initWithDictionary:[TwitterHelper fetchInfoForUsername:[twitterIds objectAtIndex:count]]]
// this calls for a method “fetchInfoForUsername” which returns an NSDictionary, but to do so, it connects to the internet using a synchronous URL fetch which would normally hold up the main thread

Later in this method you call:

[self performSelectorOnMainThread:@selector(didFinishLoadingTwitterDataWithResults:) withObject:tempArray waitUntilDone:YES];
//which returns you to the main thread at said selector…

5. In this final method, didFinishLoadingTwitterDataWithResults, which gets called once the operation finishes, you call:

self.persons = (NSArray*)result;
[self hideLoadingIndicators];
[self.tableView reloadData];
//and you can do other things like, receive the results, hide the indicators and reload a tableview.

This is very useful when downloading data from the web.