Wednesday, 21 July 2010

cocos2d as an Application Framework – Part 2

cocos2dmug[Concrete/Interesting] In the previous blog I looked at cocos2d and showed how a simple menu could be created with very little effort.

The menu is quite funky but not that exciting. cocos2d supports Sprites, so let’s build a menu with animated sprites, something like the Cover Flow you find in iTunes.

Cover Flow Menu

For a more exciting look and feel for the menu, I’m going to do a couple of things. First, I’m going to change the visual behaviour of the menu items – give them a sprite as well as text. Second, I’m going to modify the visual and input behaviour of the menu (the container for the menu items) to animate the menu items.

For the menu items, I have to first create a class that inherits from CCMenuItem:

#import <foundation/foundation.h>
#import "cocos2d.h"

@interface MenuItemComplete : CCMenuItem <CCRGBAProtocol> {
	CCLabel *title;
	CCNode<CCRGBAProtocol> *sprite;
	CCNode<CCRGBAProtocol> *spriteDisabled;
	float originalScale_;	
}

@property (nonatomic,readwrite,retain) CCNode<CCRGBAProtocol> *sprite;
@property (nonatomic,readwrite,retain) CCNode<CCRGBAProtocol> *spriteDisabled;
@property (nonatomic,readwrite,retain) CCLabel *title;

-(id) initFromImage:(NSString*)image title:(CCLabel *)title target:(id)target selector:(SEL)selector;
-(void) activate;
-(void) selected;
-(void) unselected;

@end

You can see that our class inherits from CCMenuItem and implements a couple of sprites and a label. One sprite for an enabled menu item, the other for a disabled menu item.

I’ve also declared an initializing selector called initFromImage:. In the implementation the important stuff is happening in this method and in the drawing method:

-(id) initFromImage:(NSString *)image title:(CCLabel *)t target:(id)target selector:(SEL)selector
{
	if( (self=[super initWithTarget:target selector:selector]) ) {
		
		originalScale_ = 1;
		
		self.sprite = [CCSprite spriteWithFile:image];
		self.spriteDisabled = [CCSprite spriteWithFile:image];
		[self.spriteDisabled setOpacity:80];
		[self setContentSize: [self.sprite contentSize]];
		
		self.title = t;
		self.title.position = ccp(self.contentSize.width/2 , -10);
		[self addChild: title z:100];
	}
	return self;	
}

-(void) draw
{
	if(isEnabled_)
		[sprite draw];				
	else
		[spriteDisabled draw];
}

In the init method we create the two sprites from and image past to the selector. The disabled sprite is just a copy of the normal sprite but I’ve set the opacity to 80%. Similarly, I’ve created a label from the text passed to the selector.

I’m using a variable called originalSize – this will be used later when controlling the sprite animation. Finally I’m positioning the label in this node.

Drawing is very simple, all that we do is draw the normal or disabled sprite according the the menu item’s enabled state.

We have to implement a few more selectors to support menu item behaviour, specifically, activate:, selected: and unselected:

-(void) activate {
	if(isEnabled_) {
		[self stopAllActions];
        
		self.scale = originalScale_;
        
		[super activate];
	}
}

-(void) selected
{
	if(isEnabled_) {	
		[super selected];
		[self stopActionByTag:kZoomActionTag];
		originalScale_ = self.scale;
		CCAction *zoomAction = [CCScaleTo actionWithDuration:0.1f scale:originalScale_ * 1.2f];
		zoomAction.tag = kZoomActionTag;
		[self runAction:zoomAction];
	}
}

-(void) unselected
{
	if(isEnabled_) {
		[super unselected];
		[self stopActionByTag:kZoomActionTag];
		CCAction *zoomAction = [CCScaleTo actionWithDuration:0.1f scale:originalScale_];
		zoomAction.tag = kZoomActionTag;
		[self runAction:zoomAction];
	}
}

These selectors support the visual behaviour of the menu item. When the menu item is activated, all I need to do is stop all animations, restore the active sprite to its original size and bubble up the event. When a menu item is selected I want the sprite to grow and shrink, so I stop the current animation, create a new animation action, a zoom, and set that going. Finally, unselected restores the default animation action. For a better visual effect, I've also decided to arrange the menu items horizontally through a call to the selector alignItemsHorizontally:

We can use the new class in our menu scene in place of the current menu items. We need to add a few images for our menu items and off we go:

-(id) init
{

	if( (self=[super init] )) {
		MenuItemComplete *mnuKaleidoscope = [[MenuItemComplete alloc] initFromImage:@"mnuKaleidoscope.png" title:[CCLabel labelWithString:@"Kaleidoscope" fontName:@"marker felt" fontSize:24] target: self selector: @selector(onKaleidoscope:)];
		MenuItemComplete *mnuAbout = [[MenuItemComplete alloc] initFromImage:@"mnuAbout.png" title:[CCLabel labelWithString:@"About" fontName:@"marker felt" fontSize:24] target: self selector: @selector(onAbout:)];
		MenuItemComplete *mnuQuit = [[MenuItemComplete alloc] initFromImage:@"mnuQuit.png" title:[CCLabel labelWithString:@"Quit" fontName:@"marker felt" fontSize:24] target: self selector: @selector(onQuit:)];
		CCMenu *menu = [CCMenu menuWithItems:mnuKaleidoscope, mnuAbout, mnuQuit, nil];
		[menu alignItemsHorizontally];
		[self addChild:menu];
		
		CCLabel * l = [CCLabel labelWithString:@"Main Menu" fontName:@"marker felt" fontSize:28];
		l.position = ccp(160,420);
		[self addChild:l];
		
	}
	return self;
}

The results look pretty good, the menu items behave well when selected, but no menu animation yet:

cocos2d-menu2Now I need to add behaviour to the menu. I want the menu to respond to left and right swipes, these will drag the sprites. I want the menu to wrap to give a cylindrical effect. To complete the effect, the icons should shrink and dim as they move away from the centre.

First I need to create a new class that inherits from CCMenu. At this point I’m going to cheat. João Caxaria has created a very good one that responds to touches and accelerometer. You can read more about it from:

http://www.cocos2d-iphone.org/forum/topic/139

You can find the source for the looping menu from the following link:

http://dl.dropbox.com/u/3314174/loopingmenu.zip

With that now added to the project, all I need to do now is change my scene to use a looping menu as the menu item container. The results are very impressive:

cocos2d-menu3 cocos2d-menu4 cocos2d-menu5 cocos2d-menu6 cocos2d-menu7 cocos2d-menu8

Note: I did need to make a few more changes because LoopingMenu was designed for landscape, but after some simple tweaks, it all works well in portrait.

In Part 3

In the next part I will start to delve into the world of cocos2d / UI Kit hybrids. I will be implementing a modal dialog that uses standard UI Kit elements like UIViewController and a XIB resource file.

4 comments:

  1. HI;
    Wondering if you could post a sample project of this tutorial. It's really good stuff. I did find a few things like ccrgbaprotocol should beCCRGBAProtocol and you need CCProtocols.h. At least that is with current version of cocos2d. Thanks!
    -Jim

    ReplyDelete
  2. Thank JDM, I will post a sample at the end of this series.

    Yep, corrected the CCRGBAProtocol typos. CCProtocols.h is included from cocos2d.h.

    ReplyDelete
  3. I've now posted the last part of this series. I've also posted some demo code for the project on code.google.com:

    http://code.google.com/p/iphone-cocos2d-application-framework/

    Hope you find this useful.

    ReplyDelete
  4. Thanks for nice info.

    BTW, what is technique do u use for accelerometer handling. I mean next lines:

    float xTilt=5.0f*x/tVectorLength;
    accelerometerVelocity = (accelerometerVelocity * 4.0 + xTilt) / 5.0;

    Strange coefficients and formulas.

    ReplyDelete