Friday, October 8, 2010

Making Complex Animations with Cocos2D

Cocos2D for iPhone is an excellent game engine for 2D game development on the iPhone platform. One of the strengths of the device is it's many built in actions and node types, as well as it's scheduling system, which allow one to create complex visual effects - be they animations or otherwise. In this post, I will talk about some techniques I have discovered for creating animations and effects in Cocos2D. A companion youtube video was also created as I used some of these techniques to create a first pass on an animation for a real iPhone game in development, currently code named "Furious Tactical". I was working on converting this flash animation into code.

Ingredients:

  • Scheduler

  • Color Layer

  • Actions



The Scheduler

In any Cocos2D node that is currently added to a scene, you may schedule and unschedule events. For my animation, I created a separate attack overlay layer that holds the animation, and sits above the game layer. As such, I could schedule and unschedule events right into the layer to control it.

Looking at the flash animation, you can see that there are several steps that must be followed:
1) Attacking unit slides in
2) Attacking unit name appears above it
3) Defending unit slides in
4) Defending unit name appears above it
5) Units able to attack pull back
6) Units able to attack charge at enemy unit
7) An animation/effect plays in front of attack unit
8) Screen shakes and flashes
9) Dead units fly off the screen
10) Surviving units grow and fade out

If I were to rough this out in my layer, I might have code that looks something like this:


-(id) init
{
if((self = [super init]))
{
[self schedule:@selector(slideInAttackingUnit:)];
//Store any parameters, create objects, etc.
}
}

-(void) slideInAttackingUnit:(ccTime) elapsedTime
{
[self unschedule:@selector(slideInAttackingUnit:)];

//Do slide code

[self schedule:@selector(attackingUnitNameAppears:) interval:0.5f];
}

//...etc...


When Cocos2D schedules an event on a node, it will call that event whenever the interval passes. (If no interval is supplied, it will instead call that event every frame). In the case of this animation, we only want each event to happen once, so we unschedule the current method as soon as it is entered and then schedule the following one.

I'm sure that the scheduler is familiar to most Cocos2D developers so I'll move on to the next section.

Color Layer

If you pay close attention to the flash animation, you will see that there is actually a very short white flash when the units collide. You will also notice that there is a bit of a blueish overlay when it first starts running. Both of these can easily be accomplished using a CCColorLayer.

Placing a Color Layer is incredibly simple; you simply initialise it with the colour you want it to be, and add it to your scene, as below:

CCColorLayer *backgroundLayer = [[CCColorLayer alloc] initWithColor:ccc4(128, 128, 255, 100)];
[self addChild:backgroundLayer];
[backgroundLayer release];


I typically use alloc/init instead of the convenience methods where available, just to have a little more control over memory. It's a stylistic choice and except in very tight memory situations, not entirely necessary. By setting the alpha to something less than a full GLByte of 255, it can function is a nice barrier between your current layer and what's behind.

For the white flash, using a ColorLayer in conjunction with the scheduler is quite effective. Consider the following:

-(void) addFlash
{
flashLayer = [[CCColorLayer alloc] initWithColor:ccc4(255, 255, 255, 255)]; //flashLayer is an instance variable
[self addChild:flashLayer];
[flashChild release];

[self schedule:@selector(removeFlash:) interval:0.05f];
}

-(void) removeFlash:(ccTime) elapsedTime
{
[self unschedule:@selector(removeFlash:)];

[self removeChild:flashLayer];
flashLayer = nil; //will be removed from memory - we don't want dangling pointers!
}


This simply adds and removes a white color layer very quickly to simulate a flash. Simple!

Actions

Cocos2D comes with a variety of actions. Ignoring animations, the ones I find myself using most are CCMoveTo/By, CCScaleTo/By, CCRotateTo/By, and CCFadeIn/Out.

The difference in the transform actions, for example CCMoveTo/By, is absolute versus relative. CCMoveTo moves an object to an absolute position; CCMoveBy to a relative position. For those who don't know what that means, the names help make sense of it, but here's a quick example: Imagine that you have an object at position 200, 200. If You were to apply CCMoveTo(100, 100) to it, it would move to 100, 100. Whereas, if you were to apply CCMoveBy(100, 100) to it, it would move to 300, 300.

It is often useful to sequence many actions together to save code; for example, in order to give the unit portriats a bit of a jump back, I use the following code:


leftSprite.position = ccp(-leftSprite.contentSize.width / 2,
leftSprite.contentSize.height / 2);

CCDelayTime *delay = [CCDelayTime actionWithDuration:leftDelay];
CCMoveTo *move1 = [CCMoveTo actionWithDuration:0.23f position:ccp(leftSprite.contentSize.width / 2, leftSprite.contentSize.height / 2)];
CCMoveTo *move2 = [CCMoveTo actionWithDuration:0.02f position:ccp(leftSprite.contentSize.width / 2 - 5, leftSprite.contentSize.height / 2)];
[leftSprite runAction:[CCSequence actions:delay, move1, move2, nil]];


(leftDelay is a value I use to control which of the sprites appears first).

Take note of two things; first, the CCDelayTime at the beginning. This is useful if you want to add a delay to your animation. Next, check out the CCSequence; this allows you to string a bunch of actions together, and they will then happen one after another. CCDelayTime is pretty much only useful when used in conjunction with CCSequence, as it literally does nothing. When you have listed all of your objects, you must ad nil (the sentinel) so that the action knows to expect no more. If you don't do so, you will get the warning, "Missing sentinel in function call".