Thursday, 22 July 2010

cocos2d as an Application Framework – Part 3

cocos2dmug[Concrete/Interesting] It’s all starting to come together. We have a application and a very interesting look and feel for the menu.

Now I want to really mix it up. I want to display something similar to that provided by UIAlertView, but the one from Apple just doesn’t fit the look and feel of the application under the cocos2d framework.

Custom Alert in UI Kit

Implementing a modal alert is relatively simple, I need create a XIB file with a single UIView:

cocos2d-alert1

My alert view has a title, message body and two buttons, all in a nice warm look and feel:

cocos2d-alert2

These tie back to the class definition as show in the code below:

#import <UIKit/UIKit.h>
#import "ColourfulButton.h"

@interface ModalAlert : UIViewController {
	IBOutlet UILabel *alertTitle;
	IBOutlet UILabel *alertMessage;
	IBOutlet ColourfulButton *alertCancel;
	IBOutlet ColourfulButton *alertOk;
	int nResult;  
}

@property (nonatomic, retain) UILabel *alertTitle;
@property (nonatomic, retain) UILabel *alertMessage;
@property (nonatomic, retain) ColourfulButton *alertCancel;
@property (nonatomic, retain) ColourfulButton *alertOk;
@property (nonatomic) int nResult;

- (IBAction)pressedOk;
- (IBAction)pressedCancel;

@end

int createModalAlert(NSString *title, NSString *msg, NSString *cancel, NSString *ok);  

Before I move on, let’s have a little look at this class definition. First, there is a global method called createModalAlert(). This is the entry point that will be called to show an alert. It takes a title string, a message string and two button strings.

The class is simply an extension to UIViewController, used to manage the UIView and tie the controls to class members.

This is all standard UIKit stuff.

To make things look a bit more exciting I done 5 things:

  • Use a common application font, Marker Felt. This looks good as an application font.
  • Used a colourful background for my view.
  • Used a UINavigationBar to provide a nice header for the view and a placeholder for the title
  • Used a sub-class for the buttons called ColourfulButton. This gives the buttons a better look and feel.
  • Added rounded corners to the bottom of the dialog

Integrating with cocos2d

Ideally, I want a single modal entry point that loads the dialog, handles any processing, cancels the dialog on pressing a button and returns the button id of the pressed button.

The entry point createModalAlert() does this for us:

int createModalAlert(NSString *title, NSString *msg, NSString *cancel, NSString *ok)  
{  
	ModalAlert *view = [[ModalAlert alloc] initWithNibName:@"ModalAlert" bundle:nil];
	CGSize offSize = [UIScreen mainScreen].bounds.size;   
	CGPoint offScreenCenter = CGPointMake(offSize.width / 2, offSize.height /2);   
	view.view.center = offScreenCenter;
	view.view.alpha = 0.0f;
	view.nResult = -1;
	view.view.layer.cornerRadius = 15;
	
	view.alertTitle.text = title;
	view.alertMessage.text = msg;
	
	[view.alertCancel setTitle:cancel forState:UIControlStateNormal];

	if (ok != nil)
		[view.alertOk setTitle:ok forState:UIControlStateNormal];
	else 
		[view.alertOk setHidden:YES];
	
	
	[[CCDirector sharedDirector] stopAnimation];
	
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:0.5];
	view.view.alpha = 0.95f;
	[[[CCDirector sharedDirector] openGLView] addSubview:view.view];
	[UIView commitAnimations];
	
    	while ((!view.view.hidden) && (view.view.superview!=nil))  
    	{  
		[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:0.1]];
    	}  
	
	int nResult = view.nResult;
    	[view release];  
	[[CCDirector sharedDirector] startAnimation];
    	return nResult;  
}  

The model method does a number of important things:

  • Create and position the UIView. I’m loading the view from ModaAlert.xib and locating the centre of the view in the centre of the scene.
  • Setup the text for the title, message and buttons. Note that if the function is passed nil for the second button, this button is hidden.
  • Tell cocos2d to stop all animation. This effectively pauses the application until the dialog has cleared.
  • Transition to show the view. The view is  created with its alpha set to zero.  I then set up a transition to fade in the view, add the view to the scene and commit the animation.
  • Run the modal loop. Because this is a modal dialog, we need to handle the message processing in this method. We use an NSLoop for this purpose and run until the view is hidden or removed.
  • Clean and return. Once the view has been removed, we exit the message loop, pick up the return result, start cocos2d animation and return the result.

The modal loop is controlled by the visibility of the view. To tie this into the button action and to set up the return values, we have the following implementations for the button events:

- (IBAction)pressedOk {
	nResult = 1;
	[self.view setHidden:YES];
}

- (IBAction)pressedCancel {
	nResult = 0;
	[self.view setHidden:YES];
}

That’s it – nothing more is required to present a modal view under cocos2d. As a test, I’m calling the dialog from the onQuit: selector:

-(void)onQuit: (id) sender
{
	if (createModalAlert( @"Quit", @"Are you sure you want to exit?", @"No", @"Yes")==1)	//Exit
		exit(0);
}

The results look great:

cocos2d-alert3Transitions

Here we are using a UIKit animation to show the dialog. cocos2d supports a number of transition for Sprites, layers and scenes. Transitions include rotates, slides, fades, jumps, zooms and flips.

A nice technique is to have a splash screen that transitions into the menu. This is very simple. All you need to do is create a scene with a single layer and sprite on that layer. The sprite is set to cover the entire screen:

#import "LoaderScene.h"
#import "MenuScene.h"

@implementation LoaderScene

+(id) scene
{
	CCScene *scene = [CCScene node];
	LoaderScene *layer = [LoaderScene node];
	[scene addChild: layer];
	return scene;
}

-(id) init
{
	
	if( (self=[super init] )) {
		
		CCSprite *sprite = [CCSprite spriteWithFile:@"Default.png"];
		CGSize size = [[CCDirector sharedDirector] winSize];
		sprite.position = ccp(size.width/2,size.height/2);
		[self addChild: sprite z:0];
		[self schedule: @selector(done:) interval:2.5];
	}
	return self;
}

-(void)done:(ccTime)dt
{
	[self unschedule:@selector(done:)];
	[[CCDirector sharedDirector] replaceScene:[CCFlipAngularTransition transitionWithDuration:0.5f scene:[MenuLayer scene]]];
}

- (void) dealloc
{
	[super dealloc];
}

@end

By making this scene the first scene in your application, the scene is loaded, centred and added to the layer and made visible.

Notice also that after creating and positioning the splash screen sprite, a timer is created. This is done using cocos2d scheduling. It is advisable to use this mechanism rather than NSTimer because cocos2d scheduling is cocos2d aware.

When the timer fires, I simply stop the timer and change the scene using the replaceScene: selector. This selector allows us to specify a transition to use. I’ve used a CCFlipAngularTransition. Have fun!

In Part 4

In the next part we will look at a scene that support a UIImagePickerController view.

No comments:

Post a Comment