How to Use Animations and Sprite Sheets in Cocos2D 2.X

来源:互联网 发布:产品目录设计软件 编辑:程序博客网 时间:2024/06/03 17:35

http://www.raywenderlich.com/32045/how-to-use-animations-and-sprite-sheets-in-cocos2d-2-x


This post is also available in: Japanese

Smoky says: Only you can start this bear!

Smoky says: Only you can start this bear!

Update 3/12/2013: Fully updated for Cocos2D 2.1-rc0a, Texture Packer 3.07, ARC, Retina Displays, and Modern Objective-C style (original post by Ray Wenderlich, update by Tony Dahbura).

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 2.x. You asked for it, you got it!

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.

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

Before getting started be sure to install the latest “unstable” Cocos2D 2.x libraries by downloading them from the official Cocos2D-iPhone home page. The How To Make A Simple iPhone Game tutorial has detailed installation instructions if you need them.

Let’s start by creating a Xcode skeleton for our project – create a new project with the Cocos2D 2.x iOS project template and name it AnimBear.

We are going to use ARC in this project, but by default the template isn’t set up to use ARC. Luckily, fixing it is really easy. Just go to Edit\Refactor\Convert to Objective-C ARC. Expand the dropdown and select only the last four files (main.mAppDelegate.m,HelloWorldLayer.m, and IntroLayer.m), then click Check and finish the steps of the wizard.

Choose files to convert to ARC

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 you will be using.

These images are saved in the maximum required resolution – for an iPad with a retina display (4X). In this tutorial, you will create the iPhone retina (2X) and iPhone non-retina (1X) images from these.

You could just add these directly to your Cocos2D 2.x 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 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!

Note: Want to learn more? Check out this polished and amusing video all about Sprite Sheets!

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 Andreas Loew has developed an amazing application called Texture Packer that does this automatically for you!

Texture Packer To The Rescue!

If you don’t have it already, go ahead and download a copy of Texture Packer. There is a lite version that will work for this tutorial but you will quickly see the advantages of this great tool and want to use the full featured version.

Once you have the app installed, go to File\New and you will see a blank window appear. Just drag the folder with the images into the Texture Packer window and it will automatically read all the files.

When you do that, you’ll notice that Texture Packer will size the Sprite Sheet automatically. By default, however, all the sprites don’t fit – so change the Max Size to 4096×4096. Now everything should fit OK:

Adding bear images

You’re almost there – 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 Texture Packer sets the Trim mode to remove the transparency around a sprite.

For these images, this isn’t what you want because it would mess up the positioning of the bear for the animations. Luckily, this is easy to fix – just set the Trim mode to “None” in the Layout section of the TextureSettings. The view on the right will redraw to show this.

At this point, your window should look similar to the following:

Texture Packer 2

And that’s it! So let’s save the spritesheet image and definitions so you can use them in your app.

In the panel on the left, under Output make sure Data Format says cocos2d. Then click the button with the  next to Data File and locate your Cocos2d 2.x project’s resources folder and save the file as AnimBear-ipadhd. TexturePacker will automatically fill in the Texture File name for you.

Next, click the gear icon next to AutoSD. In the Presets dropdown, select cocos2d ipad/hd/sd and click Apply. This makes TexturePacker scale down the artwork for the iPhone retina (2x) and iPhone non-retina (1x) displays automatically.

Using AutoSD option in Texture Packer

Finally, close the popup and click Publish. TexturePacker will automatically create the sprite sheets for you – in all resolutions.

Now go back to XCode and add these to your project. Right click on the Resources folder of your project, click Add Files to AnimBear…, select all files that begin with AnimBear in the Resources folder, and add them to your project. At this point your Resources group should look like this:

Adding the sprite sheets to your project

While you’re at it, click on AnimBear.plist in XCode to see what Texture Packer did for you. 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? Sure beats doing this by hand:]

Screenshot of sprite sheet plist generated by Texture Packer

But what would be even cooler is an animated bear! So let’s get to it!

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!

Screenshot of a simple animation with Cocos2D

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]; lineself.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.

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 you’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!

原创粉丝点击