Friday, 23 July 2010

cocos2d as an Application Framework - Part 4

cocos2dmug [Concrete/Interesting] So far I think the experiment to use cocos2d as an application framework has been successful. With little effort we have a very nice look and feel, excellent menu behaviour on an iPhone and a charming looking popup dialog.

Better than that, it seems relatively easy to mix UI framework into cocos2d – UI Views for example seem to behave well when added as nodes to a scene.

In this last part, I’m going to try something a little more challenging. What I want to build is a sexy About box. I ;want to mix animation, a scrolling text view and a background camera view using UIImagePickerController.

Containing cocos2d in a UIView

To show a camera surface as the background of our cocos2d scene, we are going to have to place the cocos2d as a subview of our window’s main view. We also need to make the cocos2d scene transparent. This is a little tricky but here’s how.

Look again at the selector applicationDidFinishLaunching: in the application delegate. The delegate manages the application window UIWindow *window, but there is no sign of the initialisation code for this window. That’s because the macro CC_DIRECTOR_INIT(); is doing all the work. The code behind this macro is:

window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
    [CCDirector setDirectorType:kCCDirectorTypeNSTimer];

CCDirector *__director = [CCDirector sharedDirector];
[__director setDeviceOrientation:kCCDeviceOrientationPortrait];
[__director setDisplayFPS:NO];
[__director setAnimationInterval:1.0/60];

EAGLView *__glView = [EAGLView viewWithFrame:[window bounds] pixelFormat:kEAGLColorFormatRGB565 depthFormat:0 preserveBackbuffer:NO];

[__director setOpenGLView:__glView];
[window addSubview:__glView];
window makeKeyAndVisible];

As you can see, an application window is allocated and initialized madeKeyAndVisible. Also a share CCDirector object is created and the OpenGL view is set to an EAGLView.

With a little modification we can inject a UIView between the window and the OpenGL view:

- (void) applicationDidFinishLaunching:(UIApplication*)application
{
	window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

	if(![CCDirector setDirectorType:kCCDirectorTypeDisplayLink])
		[CCDirector setDirectorType:kCCDirectorTypeNSTimer];

	CCDirector *director = [CCDirector sharedDirector];
	[director setDeviceOrientation:kCCDeviceOrientationPortrait];
	[director setDisplayFPS:YES];

	[director setAnimationInterval:1.0/60];

	EAGLView *glView = [EAGLView viewWithFrame:[[UIScreen mainScreen] bounds] pixelFormat:kEAGLColorFormatRGBA8 depthFormat:0 preserveBackbuffer:NO];
	[director setOpenGLView:glView];

	[glView setMultipleTouchEnabled:YES];
	glView.opaque = NO;
	glClearColor(0.0f,0.0f,0.0f,0.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	overlay = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
	overlay.opaque = YES;
	overlay.backgroundColor = [UIColor blackColor];
	[overlay addSubview: glView];

	[window addSubview:overlay];
	[window makeKeyAndVisible];

	[CCTexture2D setDefaultAlphaPixelFormat: kTexture2DPixelFormat_RGBA8888];
	[director runWithScene: [LoaderScene scene]];
}

As you can see, I’m creating a UIVew called overlay and adding this to the window after making the OpenGL view a child of the overlay view. There are a couple of other tricks in this piece of code. By default the OpenGL view is created opaque. If this is the case, we won’t be able to see through the OpenGL view to the UIVIew layer below, so any camera surface rendered to this overlay will be obscured. To fix this we have to set the glView.opaque = NO, set a clear colour with zero alpha and clear the buffer before any rendering. Finally, we need to set the pixelFormat:kEAGLColorFormatRGBA8 when creating the EAGLView.

So now we have an UIView containing the a transparent background EAGLView.

Funky About Box

Finally, we are ready to put the about box together. I’m going to build three layers. I’m going to put three ‘cloud’ sprites onto the scene and set them animating. Next I will create a UIImagePickerController and attach this to the Overlay view, placing it below the EAGLView. Finally, I will create a UITextView and place this on top of the EAGLView:

-(id) init
{
	
	if( (self=[super init] )) {
		
		self.isTouchEnabled  = YES;
		CGSize windowSize = [[CCDirector sharedDirector] winSize];
		
		CCSprite *cloud1 = [CCSprite spriteWithFile:@"cloud.png"];
		cloud1.position = ccp(-64, windowSize.height/2);
		[self addChild:cloud1 z:1];
		id move1 = [CCMoveBy actionWithDuration:20 position:ccp(windowSize.width+128,0)];
		[cloud1 runAction:[CCRepeatForever actionWithAction:[CCSequence actions: move1, [move1 reverse], nil]]];		
		
		CCSprite *cloud2 = [CCSprite spriteWithFile:@"cloud.png"];
		cloud2.position = ccp(80, (windowSize.height/2)+80);
		[self addChild:cloud2 z:2];
		id bounce2 = [CCMoveBy actionWithDuration:.45 position:ccp(0,8)];
		[cloud2 runAction:[CCRepeatForever actionWithAction:[CCSequence actions: bounce2, [bounce2 reverse], nil]]];		
		
		CCSprite *cloud3 = [CCSprite spriteWithFile:@"cloud.png"];
		cloud3.position = ccp(windowSize.width-80, (windowSize.height/2)-80);
		[self addChild:cloud3 z:3];
		id bounce3 = [CCMoveBy actionWithDuration:.55 position:ccp(0,8)];
		[cloud3 runAction:[CCRepeatForever actionWithAction:[CCSequence actions: bounce3, [bounce3 reverse], nil]]];		
				
		UIImagePickerController* picker=[[UIImagePickerController alloc] init];  
		picker.sourceType = UIImagePickerControllerSourceTypeCamera;
		picker.showsCameraControls = NO;
		picker.toolbarHidden = YES;
		picker.navigationBarHidden = YES;
		picker.wantsFullScreenLayout = YES;
		picker.cameraViewTransform = CGAffineTransformScale(picker.cameraViewTransform, 1, 1.4);
				
		KaleidophoneAppDelegate *delegate = (KaleidophoneAppDelegate *)[[UIApplication sharedApplication] delegate];
		[delegate.overlay insertSubview:picker.view belowSubview:[[CCDirector sharedDirector] openGLView]]; 
		
		UITextView *description = [[UITextView alloc] initWithFrame:CGRectMake(20,(windowSize.height/2)-100,windowSize.width-40,200)];
		description.backgroundColor = [UIColor clearColor];
		description.text = @"This is some text that is shown in the text field.\r\rThis text is very long spanning a number of lines.\r\rorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
		[description setEditable:NO]; 
		description.font = [UIFont fontWithName:@"Marker Felt" size:24.0f];
		description.textColor = [UIColor whiteColor];
		description.showsHorizontalScrollIndicator = NO;
		description.alwaysBounceVertical = YES;
		[delegate.overlay insertSubview:description aboveSubview:[[CCDirector sharedDirector] openGLView]]; 
		[description release];
		
	}
	return self;
}

The Results

Here are the screen shots for the funky About box:

cocos2d-about1 Ooh, a camera view of my TV with animated clouds and the About text.
Look, the clouds move. cocos2d-about2
cocos2d-about3 And the text scrolls

Conclusion

Ok, not perfect but an excellent start. Some very funky user interface components and at very little cost. Some things need more work, the EAGLView animation freezes when handling the touch events for the UITextView, but I'm sure this can be improved upon.

For anyone interested, I’ve published the source code for the demo application on code.google.com:

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

Have fun experimenting with cocos2d and UIKit.

7 comments:

  1. Hi,

    Thanks for nice tutorial. I noticed that there is no cloud.png in resource folder on svn. Could u add please?

    ReplyDelete
  2. Hi, I've just updated the code on Google and added the cloud.png file. Cheers.

    ReplyDelete
  3. hi , when i scroll the text view, the openGL view freezes (FPS freezes and clouds are not animating).

    They all return to animate as soon as i stop scrolling the text.

    Any ideas?

    ReplyDelete
  4. Hi, Thanks for this post. I have a problem with your project. I can't run it successfully. The error is: GDB: Program received signal: "SIGABRT".
    How can I fix this bug. Thanks

    ReplyDelete
  5. i get an -[AppDelegate overlay]: unrecognized selector sent to instance 0x232d10
    error, any idea whats going on?

    ReplyDelete
  6. I see there are a few problems with the code. The code is by way of illustration and I've been able to use this pattern in a commercial app. I think you need to keep with it and iron out any problems you find. Good luck with the code :)

    ReplyDelete
  7. thanks for sharing this.... but am not able to take snap in above code.......... what to do for take snap.......

    ReplyDelete