nathandemick.com

Understanding “scenes” in cocos2d-iphone

One of the building blocks of a cocos2d app is the "scene" object. In any game, you'll have multiple scenes that help break apart your code into manageable chunks. Each scene contains discrete bits of game logic; for example, your project could consist of a title scene, game options scene, and gameplay scene. Each scene, in turn, contains one or more "layers." A layer is where you will actually do most of your programming to make stuff happen. For example, your "GameplayScene" might have two layers: an "ActionLayer" and a "HUDLayer." Both layers can be (obviously) stacked on top of each other, so that the HUD always appears over the gameplay. The default cocos2d template project creates a file called HelloWorldScene.m that automatically creates a scene and then adds a layer to it. You can basically copy this method if you find that you only need one layer for each scene (to be honest, for most games, you'll probably only need one layer per scene).

To navigate through your game's UI, you'll make calls to the cocos2d Director singleton to replace the currently active scene; something like [[CCDirector sharedDirector] replaceScene:[MyAwesomeScene node]]; Most likely this call would be in a method that is run in response to a button being tapped, or after a level is completed so the player can return to a level select screen. You can also switch your scenes using some nifty transition effects that are built in to cocos2d. These are called like so: [[CCDirector sharedDirector] replaceScene:[CCFlipXTransition transitionWithDuration:0.75 scene:[MyAwesomeScene node]]]; You can see a list of transitions by looking at the "CCTransition.h" file in the "cocos2d Sources/cocos2d" folder in your Xcode project.

As an example, I'll extend the default cocos2d template with an additional scene that I created, and then tie everything together with buttons that transition from one scene to the other. First off, create a new Xcode project and have it use the cocos2d template. I called my project "SceneExample." Build and run this project and you'll see the normal Hello World! text appear. So far, so good. Now let's add a new scene to the project. In the Groups & Files gutter, right-click on the Classes group, and select the Add > New File... option. In the window that pops up, choose to have your new class extend the CCNode class (under User Templates), and name the new file "MyAwesomeScene.m" (or whatever you want, really). Click Finish to add the new class files to your project.

Next, select the MyAwesomeScene.h header file, and make a few changes:

#import "cocos2d.h"

@interface MyAwesomeScene : CCScene {}
@end

// Extending CCColorLayer so that we can specify a
// default background color other than black
@interface MyAwesomeLayer : CCColorLayer {}
@end

The template we used automatically made MyAwesomeScene extend CCNode, but we actually want it to extend CCScene. In addition, I'm defining the new layer that I want to add to my scene here. This is basically just for convenience... if you want, you could put this code in MyAwesomeLayer.h/MyAwesomeLayer.m instead. Normally, I would have my layer class extend CCLayer, then add a .png as a background for whatever I wanted displayed. However, for this example I was lazy and just wanted to have a solid colored background on the new scene, so it'd be easy to see the neat-o transition effects. So I'm having the layer extend CCColorLayer. It's the exact same as a CCLayer, but when you initialize it, you can specify a RGBA value for the background. Awesome, right? Next, open up the MyAwesomeScene.m file. Here we'll define the implementation for the scene and layer that we put in the header:

#import "MyAwesomeScene.h"
#import "HelloWorldScene.h"

@implementation MyAwesomeScene

- (id)init
{
	if ((self = [super init]))
	{
		// All this scene does upon initialization is init & add the layer class
		MyAwesomeLayer *layer = [MyAwesomeLayer node];
		[self addChild:layer];
	}

	return self;
}

- (void)dealloc
{
	// Nothing else to deallocate
	[super dealloc];
}

@end

This is the code for the scene. It's about as bare bones as it gets. In the "init" method, which gets called when the class is created, we simply declare and create a new layer, then add it to the scene. Layers are set to auto-release their memory when no longer in use, so we don't have to do anything extra in the "dealloc" method. The "node" method that is called to create the layer is a static method that each cocos2d class inherits from CCNode, the base cocos class. It creates the object, runs your "init" method and handles memory management for you. Now on to the more interesting stuff.

@implementation MyAwesomeLayer

- (id)init
{
	// Since we extended CCColorLayer instead of regular ol' CCLayer,
	// we'll init the object with initWithColor. ccc4 takes rgba arguments - in
	// this case it's a bright green background
	if ((self = [super initWithColor:ccc4(0, 255, 0, 255)]))
	{
		// Get window size
		CGSize size = [[CCDirector sharedDirector] winSize];

		// Add a button which takes us back to HelloWorldScene

		// Create a label with the text we want on the button
		CCLabelTTF *label = [CCLabelTTF labelWithString:@"Tap Here" fontName:@"Helvetica" fontSize:32.0];

		// Create a button out of the label, and tell it to run the "switchScene" method
		CCMenuItem *button = [CCMenuItemLabel itemWithLabel:label target:self selector:@selector(switchScene)];

		// Add the button to a menu - "nil" terminates the list of items to add
		CCMenu *menu = [CCMenu menuWithItems:button, nil];

		// Place the menu in center of screen
		[menu setPosition:ccp(size.width / 2, size.height / 2)];

		// Finally add the menu to the layer
		[self addChild:menu];
	}

	return self;
}

- (void)switchScene
{
	// Create a scene transition that uses the "RotoZoom" effect
	CCTransitionRotoZoom *transition = [CCTransitionRotoZoom transitionWithDuration:1.0 scene:[HelloWorld scene]];

	// Tell the director to run the transition
	[[CCDirector sharedDirector] replaceScene:transition];
}

- (void)dealloc
{
	// Nothing else to deallocate
	[super dealloc];
}

@end

This is the implementation for the layer, which is in the same MyAwesomeClass.m source file. You can see that we have all our code to actually display stuff on screen here. Don't worry too much about the code that creates a menu and button; one good thing about Objective-C is that method calls are so verbose that you can pretty easily figure out what is going on. The only thing I'll mention now is that you can't directly display a button in cocos2d; you have to add the button to a menu, then display the menu. The real point of interest here is the switchScene method, which creates a transition object and then tells the CCDirector singleton to run it. You can see that when I create my transition object, I specify a time duration for the transition to run over (one second) and the scene that I want to transition to (the default scene already in the project). There are a number of transition types... I'll leave it as a reader exercise to try out all the different effects.

OK, so the new class is created, which displays a button that transitions back to the first scene in the app. However, we'll have to modify that first scene a bit so we can transition to the new scene. In HelloWorldScene.m, make the following additions/changes:

// on "init" you need to initialize your instance
-(id) init
{
	// always call "super" init
	// Apple recommends to re-assign "self" with the "super" return value
	if( (self=[super init] )) {

		// create and initialize a Label
		CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];

		// ask director the the window size
		CGSize size = [[CCDirector sharedDirector] winSize];

		// position the label on the center of the screen
		//label.position =  ccp( size.width /2 , size.height/2 );

		// add the label as a child to this Layer
		//[self addChild: label];

		CCMenuItemLabel *button = [CCMenuItemLabel itemWithLabel:label target:self selector:@selector(switchScene)];

		CCMenu *menu = [CCMenu menuWithItems:button, nil];

		[menu setPosition:ccp(size.width / 2, size.height / 2)];

		[self addChild:menu];
	}
	return self;
}

- (void)switchScene
{
	// Set up a "FlipY" transition that moves to MyAwesomeScene
	CCTransitionFlipY *transition = [CCTransitionFlipY transitionWithDuration:1.0 scene:[MyAwesomeScene node]];

	// Tell the director to replace the currently running scene
	[[CCDirector sharedDirector] replaceScene:transition];
}

What we're doing here is slightly modifying the existing code. Instead of adding the label directly to the layer, we're shoving the label inside a button, shoving the button inside a menu, then adding the menu to the layer. The button is set up to call the switchScene method, which transitions to the new scene we created.

And with that, we're done! Build and run the project, then click on the "Hello World" text that pops up. You'll see a transition to the new scene. Click the "Tap Here" text to go back to the original scene. It's a basic example, but you can see how the code here could be expanded in a larger project. Download a .zip with the project files, iff'n you'd like. If there are any points that are unclear, don't hesitate to let me know in the comments.

· 6 comments


Comments

Detecting touch events in cocos2d-iphone | Ganbaru Games wrote on :

[...] the previous tutorial I wrote about scenes and layers, the basic structural classes of a cocos2d app. In this next [...]

Menus and buttons in cocos2d | Ganbaru Games wrote on :

[...] another look at the “scenes” tutorial. Now say that we have two scenes and want to flip back ‘n forth between them, and we’d [...]

Gaurav_Soni12345 wrote on :

Thanks... very helpful can u tell the difference between +(id)init {} and -(id)init {}

Nathan wrote on :

Yeah, any method marked with a "+" is a "class method", while "-" signifies an "instance method". Check out this explanation on Stack Overflow: http://stackoverflow.com/questions/1053592/objective-c-class-vs-instance-methods/1053646#1053646

Ben wrote on :

Hi, Nice tutorial, but could you update it? :)

Chinab wrote on :

Nice Tutorial Thanks a LOT :)