Notes on Cocos2d iPhone Development
Recently I've been using the Cocos2d iPhone library for game development on the iPhone, and it's a really fantastic library to work with. There isn't a whole lot of available source to look at though, so I've put together a few snippets and thoughts about usage.
Overview of Cocos2d iPhone
Cocos2d iPhone is Ricardo Quesada's port of the Cocos2d library to Objective-C. The original is actually a Python library, and is intensely awesome. The ObjC port can't be run in a REPL, so it is slightly less intensely awesome, but is still the cure to what ails game development on the iPhone.
When I was working on my first game, Processed Tower Defense, I learned a tremendous amount over the course of the project. I rewrote it a couple of times with better designs, and towards the end felt like I was finally starting to unlock the gates to magical and rare realms of game programming knowledge. As a result, when I started programming a game on the iPhone, my first step was to port my overall design from JavaScript to ObjC. There are certain... mismatches between the two, but it was working out well enough. That is, until we ran into OpenGL, which was something like coming upon a family of skunks blocking your trail while hiking. I mean, you could walk through them. But, damn it, do you have to?
The documentation for OpenGL on the iPhone wasn't copious, and began to hinder progress. I imagine the current docs are sufficient for someone who is already fluent in OpenGL, which is perhaps their intended audience, but it left me floored. Which was something of a problem. Fortunately, the wise Luke Hatcher suggested that we migrate the project from the adhoc framework built by yours truly, to Cocos2d iPhone. And lo, out of the box, Cocos2d already had all my hard-earned game knowledge (and more) thoughtfully integrated into a cohesive package.
The rest is history, albeit not history that anyone knows or cares about.
Key Classes
CocosNode is the most important class you'll be working with. If an object does any of a) move, b) display or c) change then you'll want to be using a CocosNode to represent it.
The most useful CocosNode methods are:
// creation +(id)node;
// adding children nodes -(id)add: (CocosNode)node; -(id)add: (CocosNode)node z:(int)z; -(id)add: (CocosNode)node z:(int)z name:(NSString)name;
// retrieving children nodes -(CocosNode)get: (NSString )name;
// removing children nodes -(void)remove: (CocosNode)node; -(void)removeByName: (NSString)name;
// scheduling periodic updates -(void)schedule: (SEL)s interval:(ccTime)i;
// assigning actions (movement, animation, etc) -(Action)do: (Action)action; -(void)stopAction: (Action*)action; -(void)stopAllActions;
and some same usage looks like:
// creating CocosNode * parentNode = [CocosNode node]; CocosNode * childNode = [CocosNode node]; CocosNode * childNode2 = [CocosNode node];
// connecting [parentNode add:childNode z:0 name:@"childOne"]; [parentNode add:childNode2];
// accessing [parentNode get:@"childOne"];
// using actions [parentNode do:[ScaleTo actionWithDuration:.1 scale:1.5]]; cpVect point = cpv(50,100); [parentNode do:[MoveTo actionWithDuration:0.5 position:point]];
// disconnecting [parentNode removeByName:@"childOne"]; [parentNode remove:childNode2];
The most common way of taking advantage of CocosNode is to subclass it, add a custom init method, and schedule a function to be periodically called.
A simple example might look like:
#import "CocosNode.h" @interface LogNode : CocosNode {} -(void)tick: (ccTime)dt; @end
@implementation LogNode -(id)init { self = super[init]; if (self) { [self schedule:@selector(tick:) interval:100]; } return self; }
-(void)tick: (ccTime)dt { NSLog(@"%@", self); } @end
Sprite is a subclass of CocosNode that makes displaying graphics very easy. Usage is as simple as:
NSString * img = @"spaceship.png"; Sprite * sp = [Sprite spriteFromFile:img]; sp.position = cpv(100,24); [someNode add:sp];
You'll use a lot of these. My experience is that it is simpler to subclass CocosNode and add Sprites to it, rather than subclassing Sprite directly. Among other things, that allow you to combine multiple images into one node and transform them consistently by transforming their parental node.
Action are transformations that are applied to CocosNodes. There are a number of actions, split into two groups: IntervalAction and InstantAction. As you might imagine, IntervalActions occur over a period of time (although you can always set that period to be zero seconds, making the action occur instantly), whereas InstantActions occur instantly.
Some of the useful actions are:
- ToggleVisiblity makes a CocosNode appear/disappear.
- RotateBy rotates a CocosNode a number of degrees.
- MoveTo moves a CocosNode to a position.
- ScaleTo scales a CocosNode by a factor.
- FadeTo set a CocosNode's opacity.
They are used like this (some have slightly different parameters for their constructors, so be sure to check the docs):
[parentNode do:[ScaleTo actionWithDuration:.1 scale:1.5]]; [parentNode do:[MoveTo actionWithDuration:0.5 position:cpv(50,100)]];
Director manages what is currently visible, what scene is currently running, whether or not game time is progressing, and so on.
Useful methods are:
// it's a singleton Direction * d = [Director sharedDirector];
// Managing game [d pause]; [d resume]; [d end];
// Managing Scenes Screne * s = [Scene node]; [d runScene:s]; // start time lapse in scene [d popScene:s]; [d pushScene:s]; [d replaceScene:s]; // recommended for performance
Layer is a container of one ore more CocosNodes. A subclass of CocosNode, they are used for grouping distinct parts of your application. For example, you'll have a main menu layer, a game layer (at least one, possibly many depending on your game's dynamics), a pause menu layer, etc.
Usage is the same as with CocosNode, except initialize using
[Layer node]
instead of[CocosNode node]
. This is another class that you may find yourself subclassing frequently.Scene is a collection of one or more CocosNodes, but usually contains Layers. They are passed to the Director to determine what is currently visible/occuring in the game.
Scene * myScene = [Scene node]; Sprite * bg = [Sprite spriteFromFile:@"background.png"]; bg.position = cpv(200, 600); [myScene add:bg z:0]; Layer * myLayer = [MyCustomLayer node]; [myScene add:myLayer z:0]; [[Director sharedDirector] runScene:myScene];
Label is used for rendering text, and makes that process much much simpler than dealing with the other alternatives for text rendering on the iPhone.
Label * name = [Label labelWithString:@"Name" dimensions:CGSizeMake(90,64) alignment:UITextAlignmentCenter fontName:@"Helvetica" fontSize:10]; name.position = cpv(100,200); [someNode add:name];
There certainly are a number of other classes in Cocos2d, but those are probably the only classes you'll need to interact with to create something interesting.
Now for a few topical snippets.
Snippet 1: Making a Menu
[MenuItemFont setFontName:@"Helvetica"];
[MenuItemFont setFontSize:12];
MenuItem item1 = [MenuItemFont itemFromString: @"Up" target:self selector:@selector(up)];
MenuItem item2 = [MenuItemFont itemFromString: @"Down" target:self selector:@selector(down)] : nil;
Menu *menu = [Menu menuWithItems:item1, item2, nil];
[menu setPosition:cpv(200,200)];
[someLayer add:menu];
Snippet 2: Directing the Director
Your application delegate has a few responsibilities for informing the Director about special events. Specifically, it needs to know when to start, pause, resume and quit.
The simplest case for delivering those four notifications looks something like this (code should be in your app delegate):
- (void) applicationDidFinishLaunching:(UIApplication*)application {
// Game began...
[[Director sharedDirector] setLandscape: YES]; // optional
[[Director sharedDirector] setDisplayFPS:YES]; // optional
Scene *menu = [MyCustomMenuClass node];
[[Director sharedDirector] runScene:menu];
}
-(void) applicationWillResignActive:(UIApplication *)application {
// Incoming phone call…
[[Director sharedDirector] pause];
}
-(void) applicationDidBecomeActive:(UIApplication *)application {
// Phone call rejected…
[[Director sharedDirector] resume];
}
-(void) applicationWillTerminate: (UIApplication*) application {
// Application is ending…
[[Director sharedDirector] release];
}
Snippet 3: Detecting Clicks on Cocos Nodes
This is a fairly involved topic, because how you'll want to handle it depends entirely on how you've designed your user interface, but there are essentially two approaches:
Override the standard UIEvent touches methods for all objects that care about being clicked.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
Override the standard UIEvent touches methods for one object (probaby a subclass of Layer), and having it serve as a UI manager for all objects it contains.
The first approach is simpler, but less flexible (more difficult to manage event propagation and other custom logic). on the other hand, the second approach is more work, and doesn't fit trimly into the snippet format.
Roughly, the first approach will look something like this (in the implementation file for a subclass of CocosNode):
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];
// do stuff
}
- (void)touchesMoved:(NSSet )touches withEvent:(UIEvent )event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];
// do stuff
}
- (void)touchesEnded:(NSSet )touches withEvent:(UIEvent )event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];
// do stuff
}
Well, those are my guiding notes for the time being. I'll try to write something more comprehensive soon.