How to Use Animations and Sprite Sheets in Cocos2D 2.X
In this tutorial, you will learn how to create a simple animation of a bear walking in Cocos2D. You’ll also learn how to make them efficient by using sprite sheets, how to make your bear move in response to touch events, and how to change the direction the bear faces based on where the bear is moving. By Tony Dahbura.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Use Animations and Sprite Sheets in Cocos2D 2.X
20 mins
A Simple Animation
You’re going to start just by plopping the 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 cleaning up some of the prebuilt code that the Cocos2d 2.x template inserted. Replace HelloWorldLayer.h with the following:
#import "cocos2d.h"
@interface HelloWorldLayer : CCLayer
{
}
+(CCScene *) scene;
@end
Because you are going to use modern Objective-C capabilities the rest of your work will be in HelloWorldLayer.m. So switch over to HelloWorldLayer.m and replace the contents with the following:
#import "HelloWorldLayer.h"
@interface HelloWorldLayer ()
{
BOOL bearMoving;
}
@property (nonatomic, strong) CCSprite *bear;
@property (nonatomic, strong) CCAction *walkAction;
@property (nonatomic, strong) CCAction *moveAction;
@end
@implementation HelloWorldLayer
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild: layer];
return scene;
}
-(id) init {
if((self = [super init])) {
// TODO...
}
return self;
}
@end
At this point you’ve just emptied out the project template to create a nice blank slate (and defined a few variables you’ll need later). Build and run to make sure everything builds OK – you should see a blank screen.
There are 5 steps you will 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 “TODO” area shown by the comment.
1) Cache the sprite frames and texture
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"AnimBear.plist"];
First, you make a call to the shared CCSpriteFrameCache’s addSpriteFramesWithFile method, and pass in the name of the property list that Texture Packer generated for you. This method does the following:
- Looks for an image with the same name as the passed-in property list, but ending with “.png” instead, and loads that file into the shared CCTextureCache (in our case, AnimBear.png).
- 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.
Note that Cocos2D will automatically look for the right file based on the resolution of the device – for example, if you’re running on an iPad with a Retina display, it will load AnimBear-ipadhd.png and AnimBear-ipadhd.plist instead.
2) Create a sprite batch node
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:@"AnimBear.png"];
[self addChild:spriteSheet];
Next, you create a CCSpriteBatchNode object, passing in the image of your sprite sheet. The way sprite sheets work in Cocos2D 2.x is the following:
- You create a CCSpriteBatchNode object passing in the image file containing all of the sprites, as you 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.
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.png",i]]];
}
To create the list of frames, you simply loop through your image's names (they are named with a convention of Bear1.png -> Bear8.png) and try to find a sprite frame by that name in the shared CCSpriteFrameCache. Remember, these should already be in the cache because you called addSpriteFramesWithFile earlier.
4) Create the animation object
CCAnimation *walkAnim = [CCAnimation
animationWithSpriteFrames:walkAnimFrames delay:0.1f];
Next, you create a CCAnimation by passing in the list of sprite frames, and specifying how fast the animation should play. You are using 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.png"];
self.bear.position = ccp(winSize.width/2, winSize.height/2);
self.walkAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:walkAnim]];
[self.bear runAction:self.walkAction];
[spriteSheet addChild:self.bear];
You then create a sprite for your bear passing in a frame to start with, and center it in the middle of the screen. Next, set up a CCAnimateAction telling it the name of the CCAnimation to use, and tell the bear to run it!
Finally, you add the bear to the scene - by adding it as a child of the sprite sheet! Note that if you did not add it as a child of the spritesheet and instead added it as a child of the layer, you would not get the performance benefits (such as if you had several bears).
Done!
And that's it! So build and run the project, and if all goes well you should see your bear happily strolling on the screen!
Changing Animation Facing Direction Based on Movement
Things are looking good - except you don't want this bear meandering about on its own, that would be dangerous! Would be much better if you could control its movement by touching the screen to tell it where to go.
So make the following changes to HelloWorldLayer.m:
// Comment out the runAction method in the init method:
//[self.bear runAction:self.walkAction];
// And add this to the init method after [spriteSheet addChild:self.bear]; line
self.touchEnabled = YES;
// Add these new methods
- (void)registerWithTouchDispatcher
{
[[[CCDirector sharedDirector] touchDispatcher] 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
{
[self.bear stopAction:self.walkAction];
bearMoving = NO;
}
Starting at the beginning, you are commenting out running the walk action in the init method, because you don't want our bear moving until you tell him to!
You 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 2.X Tutorial.
When the bearMoveEnded method is called, you want to stop any running animation and mark that you're no longer moving.
As for the ccTouchEnded function, this is where the meat of your code will go. There's a lot of stuff here, so let's break it into steps like before!
1) Determine the touch location
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
Nothing new here - you just start by converting the touch point into local node coordinates using the usual method.
2) Set the desired velocity
CGSize screenSize = [[CCDirector sharedDirector] winSize];
float bearVelocity = screenSize.width / 3.0;
Here you set up a velocity for the bear to move. You will estimate that it should take about 3 seconds for the bear to move the width of the iPhone screen. Since the new models are out you need to account for the width by asking the screen for its width (480 or 568 pixels), so since velocity is distance over time it would be the width pixels / 3 seconds.
3) Figure out the amount moved in X and Y
CGPoint moveDifference = ccpSub(touchLocation, self.bear.position);
Next you need to figure out how far the bear is moving along both the x and y axis. You can do this by simply subtracting the bear's position from the touch location. There is a convenient helper function Cocos2D provides called ccpSub to do this.
4) Figure out the actual length moved
float distanceToMove = ccpLength(moveDifference);
You then need to calculate the distance that the bear actually moves along a straight line (the hypotenuse of the triangle). Cocos2D also has a helper function to figure this out based on the offset moved: ccpLength!
5) Figure out how long it will take to move
float moveDuration = distanceToMove / bearVelocity;
Finally, you need to calculate how long it should take the bear to move this length, so you simply divide the length moved by the velocity to get that.
6) Flip the animation if necessary
if (moveDifference.x < 0) {
self.bear.flipX = NO;
} else {
self.bear.flipX = YES;
}
Next, you 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, you're moving to the left and you can play the animation as-is. However, if it's moving to the right you need to flip your animation to the other way!
First instinct might be to run to your 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 - you 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 you are moving the bear to the right, you set flipX to YES.
7) Run the appropriate actions
[self.bear stopAction:self.moveAction];
if (!bearMoving) {
[self.bear runAction:self.walkAction];
}
self.moveAction = [CCSequence actions:
[CCMoveTo actionWithDuration:moveDuration position:touchLocation],
[CCCallFunc actionWithTarget:self selector:@selector(bearMoveEnded)],
nil];
[self.bear runAction:self.moveAction];
bearMoving = YES;
Next, you stop any existing move action (because you're about to override any existing command to tell the bear to go somewhere else!) Also, if you're not moving, you stop any running animation action. If you are already moving, you want to let the animation continue so as to not interrupt its flow.
Finally, you create the move action itself, specifying where to move, how long it should take, and having a callback to run when it's done. You also record that we're moving at this point by setting our instance variable bearMoving=YES.
Done!
A lot of code - but was it worth it? Build and run to see! If all works well you should be able to tap the screen to move your bear all around.