Sunday 12 February 2012

How To Use Animations and Sprite Sheets in Cocos2D


I’ve gotten a ton of requests from readers of this blog to make a tutorial on how to use animations and sprite sheets in Cocos2D. You asked for it, you got it!
In this tutorial, we will show how to create a simple animation of a bear walking in Cocos2D. We’ll also cover how to make them efficient by using sprite sheets, how to make our bear move in response to touch events, and how to change the direction the bear faces based on where the bear is moving.
If you are new to Cocos2D, you may wish to go through the tutorial series on How To Make A Simple iPhone Game With Cocos2D first, but this is not required!

Getting Started

Let’s start by creating a skeleton for our project – create a new project with the Cocos2D project template and name it AnimBear.
Next, go ahead and download some images of an animated bear made by my lovely wife.
When you unzip the file, take a look at the images – they are just individual frames of a bear that when you put them together, look like the bear is moving.
Examples of the bear images we will be using.
We could just add these directly to our Cocos2D project at this point and create an animation based on these individual images. However, there’s another way to create animations in Cocos2D that is much more efficient – by using sprite sheets.

Sprite Sheets and Bears, Oh My!

If you haven’t used sprite sheets yet, think of them as gigantic images that you put your sprites within. They come with a file that specifies the boundaries for each individual sprite so you can pull them out when you need them within the code.
The reason why these are such a good idea to use is because Cocos2D is optimized for them. If you use sprites within a sprite sheet properly, rather than making one OpenGL ES draw call per sprite it just makes one per sprite sheet.
In short – it’s faster, especially when you have a lot of sprites!
As for sprite sheets, you could actually create them yourself manually with your image editor and create the file that specifies the boundaries yourself by hand as well.
However, this would be crazy talk because Robert Payne has developed an amazing app called Zwoptex that does this automatically for us!

Zwoptex To Victory!

If you don’t have it already, go ahead and download a copy of Zwoptex from zwoptexapp.com. There’s an online Flash version and a downloadable version, but these days I use the downloadable version.
Once you have the app installed, go to File\New and you will see a blank window appear. Open up the folder where you downloaded all of the bear images, and drag them into the view.
You’ll notice that all the bear images are on top of each other. We need them to be spread out in our sprite sheet, so click “Apply” in the Layout section to sort them.
When you do that, you’ll notice that there’s not enough space in the default canvas (512×512) to fit all of the sprites, so some still overlap. So increase the space to 512×1024 in the Canvas section and click “Apply” in the Layout section to re-sort them.
We’re getting close – but notice how some of the bear images are wider than others. If you look at the original images, that isn’t the way they were made – but by default Zwoptex trims the images to remove any transparency around them.
For these images, this isn’t what we want because it would mess up the positioning of the bear for the animations. Luckily, this is easy to fix – just click Untrim in the toolbar up top, and click “Apply” in the Layout section to re-sort one final time.
At this point, your window should look similar to the following:
Screenshot of Zwoptex creating our Sprite Sheet
And that’s it! So let’s save the spritesheet image and definitions so we can use them in our app.
Click “Save .jpg” in the Export section, and save the Png to the resources folder of your project as “AnimBear.jpg”. Then click “Save .plist” in the Export section, and save the plist to the resources folder of your project as “AnimBear.plist”.
Update: When saving your files in Zwoptex, be sure to pick “Cocos2D” as the format, or else your plist will not load properly in Cocos2D! Thanks to Muhammad in the comments section for pointing this out.
Now let’s go back to XCode and add these to your project. Right click on the Resources folder of your project, click “Add\Existing Files…”, select AnimBear.jpg and AnimBear.plist from your Resources folder, and click Add.
While you’re at it, click on AnimBear.plist in XCode to see what Zwoptex did for us. You’ll see that it’s just a property list with two sections – frames and metadata. In the frames section, there is an entry for each of the images in the spritesheet, with properties inside that give the bounding box for the image within the spritesheet. Cool eh?
Screenshot of sprite sheet plist generated by Zwoptex
But what would be even cooler is an animated bear! So let’s get to it!

A Simple Animation

We’re going to start just by plopping our bear in the middle of the screen and looping the animation so he moves forever, just to make sure things are working.
So let’s start by adding a few properties we’ll need in this tutorial into HelloWorldScene.h. Make the following modifications to the file:
// Add inside the HelloWorld interface
CCSprite *_bear;
CCAction *_walkAction;
CCAction *_moveAction;
BOOL _moving;
 
// Add after the HelloWorld interface
@property (nonatomic, retain) CCSprite *bear;
@property (nonatomic, retain) CCAction *walkAction;
@property (nonatomic, retain) CCAction *moveAction;
We actually don’t need all of these properties right away, but we’re putting them in place now so we don’t have to go back and change the code later.
Now, to the fun stuff! Move over to HelloWorldScene.m and make the following changes to the file:
// At the top, under @implementation
@synthesize bear = _bear;
@synthesize moveAction = _moveAction;
@synthesize walkAction = _walkAction;
 
// In dealloc
self.bear = nil;
self.walkAction = nil;
 
// Replace the init method with the following
-(id) init {
    if((self = [super init])) {
        // Add the stuff from below!                            
    }
    return self;
}
There are 5 steps we need to take to get this animation to work, so let’s cover them one at a time. Add each of these snippets to your init method in the area shown by the comment.
1) Cache the sprite frames and texture
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
    @"AnimBear.plist"];
First, we make a call to the shared CCSpriteFrameCache’s addSpriteFramesWithFile method, and pass in the name of the property list that Zwoptex generated for us. This method does the following:
  • Looks for an image with the same name as the passed-in property list, but ending with “.jpg” instead, and loads that file into the shared CCTextureCache (in our case, AnimBear.jpg).
  • Parses the property list file and keeps track of where all of the sprites are, using CCSpriteFrame objects internally to keep track of this information.
2) Create a sprite batch node
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode 
    batchNodeWithFile:@"AnimBear.jpg"];
[self addChild:spriteSheet];
Next, we create a CCSpriteBatchNode object, passing in the image of our sprite sheet. The way sprite sheets work in Cocos2D is the following:
  • You create a CCSpriteBatchNode object passing in the image file containing all of the sprites, as we did here, and add that to your scene.
  • Now, any time you create a sprite that comes from that sprite sheet, you should add the sprite as a child of the CCSpriteBatchNode. As long as the sprite comes from the sprite sheet it will work, otherwise you’ll get an error.
  • The CCSpriteBatchNode code has the smarts to look through its CCSprite children and draw them in a single OpenGL ES call rather than multiple calls, which again is much faster.
Note: CCSpriteBatchNode used to be called CCSpriteSheet, so you may see it called that in some older code.
3) Gather the list of frames
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 8; ++i) {
    [walkAnimFrames addObject:
        [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
            [NSString stringWithFormat:@"bear%d.jpg", i]]];
}
To create the list of frames, we simply loop through our images names (they are named with a convention of Bear1.jpg -> Bear8.jpg) and try to find a sprite frame by that name in the shared CCSpriteFrameCache. Remember, these should already be in the cache because we called addSpriteFramesWithFile earlier.
4) Create the animation object
CCAnimation *walkAnim = [CCAnimation 
    animationWithFrames:walkAnimFrames delay:0.1f];
Next, we create a CCAnimation by passing in the list of sprite frames, and specifying how fast the animation should play. We use a 0.1 second delay between frames here.
5) Create the sprite and run the animation action
CGSize winSize = [CCDirector sharedDirector].winSize;
self.bear = [CCSprite spriteWithSpriteFrameName:@"bear1.jpg"];        
_bear.position = ccp(winSize.width/2, winSize.height/2);
self.walkAction = [CCRepeatForever actionWithAction:
    [CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
[_bear runAction:_walkAction];
[spriteSheet addChild:_bear];
We then create a sprite for our bear passing in a frame to start with, and center it in the middle of the screen. We then set up an CCAnimateAction telling it the name of the CCAnimation to use, and tell the bear to run it!
Finally, we add the bear to the scene – by adding it as a child of the sprite sheet! Note that if we did not add it as a child of the spritesheet and instead added it as a child of the layer, we would not get the performance benefits (such as if we had several bears).
Done!
And that’s it! So compile and run the project, and if all goes well you should see your bear happily strolling on the screen!
Screenshot of a simple animation with Cocos2D

Changing Animation Facing Direction Based on Movement

Things are looking good – except we don’t want this bear meandering about on its own, that would be dangerous! Would be much better if we could control its movement by touching the screen to tell it where to go.
So make the following changes to HelloWorldScene.m:
// Comment out the runAction method in the init method:
//[_bear runAction:_walkAction];
 
// And add this to the init method
self.isTouchEnabled = YES;
 
// Add these new methods
-(void) registerWithTouchDispatcher
{
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 
        swallowsTouches:YES];
}
 
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
 return YES;
}
 
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {    
    // Stuff from below!    
}
 
-(void)bearMoveEnded {
    [_bear stopAction:_walkAction];
    _moving = FALSE;
}
Starting at the beginning, we comment out running the walk action in the init method, because we don’t want our bear moving until we tell him to!
We also set the layer as touch enabled, and implement registerWithTouchDispatcher and ccTouchBegan. If you are curious as to the advantages of using this method rather than plain ccTouchesBegan, check out an explanation in the How To Make a Tile Based Game with Cocos2D Tutorial.
When the bearMoveEnded method is called, we want to stop any running animation and mark that we’re no longer moving.
As for the ccTouchEnded function, this is where the meat of our code is. There’s a lot of stuff here, so we’re going to break it into steps like we did before – X this time!
1) Determine the touch location
CGPoint touchLocation = [touch locationInView: [touch view]];  
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
Nothing new here – we just start by converting the touch point into local node coordinates using our usual method.
2) Set the desired velocity
float bearVelocity = 480.0/3.0;
Here we set up a velocity for the bear to move. I estimated that it should take about 3 seconds for the bear to move the width of the iPhone screen (480 pixels), so since velocity is distance over time it would be 480 pixels / 3 seconds.
3) Figure out the amount moved in X and Y
CGPoint moveDifference = ccpSub(touchLocation, _bear.position);
Next we need to figure out how far the bear is moving along both the x and y axis. We can do this by simply subtracting the bear’s position from the touch location. We use a helper function Cocos2D provides called ccpSub to do this.
4) Figure out the actual length moved
float distanceToMove = ccpLength(moveDifference);
We then need to calculate the distance that the bear actually moves along a straight line (the hypotenuse of the triangle). Cocos2D has a helper function to figure this out based on the offset moved: ccpLength, which we use here!
5) Figure out how long it will take to move
float moveDuration = distanceToMove / bearVelocity;
Finally, we need to calculate how long it should take the bear to move this length, so we simply divide the length moved by the velocity to get that.
6) Flip the animation if necessary
if (moveDifference.x < 0) {
    _bear.flipX = NO;
} else {
    _bear.flipX = YES;
}
Next, we look to see if the bear is moving to the right or to the left by looking at the move difference. If it’s less than 0, we’re moving to the left and we can play our animation as-is. However, if it’s moving to the right we need to flip our animation to the other way!
Our first instinct might be to run to our image editor and create new images for the bear facing the other direction, and use those. However Cocos2D has a much easier (and more efficient) way – we can simply flip the existing images!
The way it works, you actually set a flip value on the sprite the animation is run on, and it will cause any animation frames that is run on the sprite to be flipped as well. So in the case we are moving the bear to the right, we set flipX to YES.
7) Run the appropriate actions
[_bear stopAction:_moveAction];
 
if (!_moving) {
    [_bear runAction:_walkAction];
}
 
self.moveAction = [CCSequence actions:                          
    [CCMoveTo actionWithDuration:moveDuration position:touchLocation],
    [CCCallFunc actionWithTarget:self selector:@selector(bearMoveEnded)],
    nil];
 
[_bear runAction:_moveAction];   
_moving = TRUE;
Next, we stop any existing move action (because we’re about to override any existing command to tell the bear to go somewhere else!) Also, if we’re not moving, we stop any running animation action. If we are already moving, we want to let the animation continue so as to not interrupt its flow.
Finally, we create the move action itself, specifying where to move, how long it should take, and having a callback to run when it’s done. We also record that we’re moving at this point.
Done!
A lot of code – but was it worth it? Compile and run to see! If all works well you should be able to tap the screen to move your bear all around.
Screenshot of our updated bear animation - with touch to move and flip support!

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in the above tutorial.
At this point, you should know how to use animations in your projects. You should have some fun and experiment by creating your own animations and seeing what you can do!

0 comments:

Post a Comment

 

Copyright @ 2013 PakTechClub.