Browsing articles tagged with " cocos2d"

How to Animate Sprites in cocos2d

Apr 18, 2010   //   by Derek van Vliet   //   Development  //  41 Comments

One of the most frequently asked questions I see about cocos2d for iPhone is “how do you animate sprites?”. This was also one of the first questions we had when developing Addicus.

It’s actually quite simple to do using cocos2d’s CCSpriteSheet, CCSprite and CCAnimation classes. These classes take a texture atlas and switch between frames at regular intervals. In other words: animation!

Here is how to animate a sprite in cocos2d in 5 steps. You can also download the code for this blog post.

1. Create Your Animation Texture Atlas

First you need to combine all of the frames of your animation into a single graphic, called a texture atlas. You can do this by hand in an image editor like Photoshop, simply by copying and pasting all of the frames of your animation into a single file. Alternatively, there are atlas generating tools that will take a batch of image files and compile them into a texture atlas in just a couple of clicks.

I recommend using a Flash-based tool called Zwoptex because it is officially supported by cocos2d.

Once you’re done, you should have an image that contains all of the frames of your animation such as the one below:

Grossini Dance
Incidentally, this image is included in the cocos2d distribution.

2. Create a CCSpriteSheet

Once we have the texture atlas, it is time to get coding. The following code would go in the init method of a CCScene sub-class.

The first step in code is to create an instance of a CCSpriteSheet using your texture atlas and child it to a node in the scene. In this case we are childing it to the CCScene itself.

CCSpriteSheet *danceSheet = [CCSpriteSheet spriteSheetWithFile:@"grossini_dance_atlas.png"];
[self addChild:danceSheet];

3. Create a CCSprite Using Your CCSpriteSheet

Next we create a CCSprite that uses the texture of the CCSpriteSheet that we just created. We then child it to the CCSpriteSheet. The rect that we initialize the CCSprite with is the first frame of the animation.

CCSprite *danceSprite = [CCSprite spriteWithTexture:danceSheet.texture rect:CGRectMake(0, 0, 85, 121)];
[danceSheet addChild:danceSprite];

4. Create a CCAnimation

Next we need to create a CCAnimation instance and add all frames of the animation to it. In the case of this texture atlas, we know all of the frames are the same size and there are 14 of them, so we can use a nested loop to iterate through them all and break the loop when we finish adding frame #14.

CCAnimation *danceAnimation = [CCAnimation animationWithName:@"dance" delay:0.1f];
 
int frameCount = 0;
for (int y = 0; y < 3; y++) {
	for (int x = 0; x < 5; x++) {
		CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:danceSheet.texture rect:CGRectMake(x*85,y*121,85,121) offset:ccp(0,0)];
		[danceAnimation addFrame:frame];
 
		frameCount++;
 
		if (frameCount == 14)
			break;
	}
}

5. Run the CCAnimation on the CCSprite

Finally, we need to create a CCAnimate action instance which we can run on the CCSprite. Below, we also wrap the CCAnimate action in a CCRepeatForever action that does what you would expect: repeats the animation… forever.

The last line actually plays the animation on the sprite using the runAction message.

CCAnimate *danceAction = [CCAnimate actionWithAnimation:danceAnimation];
CCRepeatForever *repeat = [CCRepeatForever actionWithAction:danceAction];
[danceSprite runAction:repeat];

Putting it All Together

Here is what the above code looks like in a CCScene init method:

//
//  DanceScene.m
//  GrossiniDance
//
 
#import "DanceScene.h"
 
 
@implementation DanceScene
-(id)init {
	self = [super init];
 
	if (self) {
		// create the sprite sheet
		CCSpriteSheet *danceSheet = [CCSpriteSheet spriteSheetWithFile:@"grossini_dance_atlas.png"];
		[self addChild:danceSheet];
 
		// create the sprite
		CCSprite *danceSprite = [CCSprite spriteWithTexture:danceSheet.texture rect:CGRectMake(0, 0, 85, 121)];
		[danceSheet addChild:danceSprite];
 
		// position the sprite in the center of the screen
		CGSize s = [[CCDirector sharedDirector] winSize];
		danceSprite.position = ccp(s.width/2,s.height/2);
 
		// create the animation
		CCAnimation *danceAnimation = [CCAnimation animationWithName:@"dance" delay:0.1f];
 
		int frameCount = 0;
		for (int y = 0; y < 3; y++) {
			for (int x = 0; x < 5; x++) {
				CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:danceSheet.texture rect:CGRectMake(x*85,y*121,85,121) offset:ccp(0,0)];
				[danceAnimation addFrame:frame];
 
				frameCount++;
 
				if (frameCount == 14)
					break;
			}
		}
 
		// create the action
		CCAnimate *danceAction = [CCAnimate actionWithAnimation:danceAnimation];
		CCRepeatForever *repeat = [CCRepeatForever actionWithAction:danceAction];
 
		// run the action
		[danceSprite runAction:repeat];
	}
 
	return self;
}
@end

Custom cocos2d Action For Animating an AtlasSprite’s TextureRect

Sep 23, 2009   //   by Derek van Vliet   //   Development  //  1 Comment

While working on our coming-soon first iPhone game, we have been having a lot of success with cocos2d. One area in particular that we have been impressed with is the ability to run “Actions” on sprites, string them together in sequences and wrap them in modifiers such as ease-in, ease-out etc. This has made it easy to make our UI lively and animated as well as animate our game objects.

Recently we had a need to animate an AtlasSprite’s texture rect in order to pan around an atlas sprite. We ended up creating a custom cocos2d action to do this and we figured it would be a good thing to give back to the community.

This will allow you to transform an AtlasSprite’s texture rect’s origin and size over the length of a provided duration. You can also wrap this in a modifier action such as ease-in or repeater.

Here is the code

TransformTextureRect.h:

#import "cocos2d.h"
 
@interface TransformTextureRect : IntervalAction {
	CGRect fromRect;
	CGRect toRect;
}
+(id) actionWithDuration:(ccTime)d toRect:(CGRect)rect;
-(id) initWithDuration:(ccTime)d toRect:(CGRect)rect;
 
@end

TransformTextureRect.m:

#import "TransformTextureRect.h"
 
@implementation TransformTextureRect
+(id) actionWithDuration:(ccTime)d toRect:(CGRect)rect {
	return [[[self alloc] initWithDuration:d toRect:rect] autorelease];
}
 
-(id) initWithDuration:(ccTime)d toRect:(CGRect)rect {
	if ( (self=[super initWithDuration:d] ) )
		toRect = rect;
	return self;
}
 
-(id) copyWithZone: (NSZone*) zone
{
	Action *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] toRect: toRect];
	return copy;
}
 
-(void) start
{
	[super start];
	fromRect = [(AtlasSprite*)target textureRect];
}
 
-(void) update: (ccTime) t
{	
	[(AtlasSprite*)target setTextureRect:CGRectMake(fromRect.origin.x + (toRect.origin.x - fromRect.origin.x) * t,
													fromRect.origin.y + (toRect.origin.y - fromRect.origin.y) * t,
													fromRect.size.width + (toRect.size.width - fromRect.size.width) * t,
													fromRect.size.height + (toRect.size.height - fromRect.size.height) * t)];
}
 
@end

Example Usage

This transforms an AtlasSprite’s texture rect to the position (25,25) and with a width and height of 50 over the course of 1 second:

[sprite runAction:[TransformTextureRect actionWithDuration:1 toRect:CGRectMake(25,25,50,50)]];

This does something similar to the above example, only it eases in and out of the action as well.

[sprite runAction:[EaseExponentialInOut actionWithAction:[TransformTextureRect actionWithDuration:2 toRect:CGRectMake(480,0,480,320)]]];

Cocos2D and UIScrollView

Aug 21, 2009   //   by Rob Segal   //   Development  //  127 Comments

Learning now to work with a new framework is usually a challenge.  You often delve into a new API or SDK expecting things to work in a certain manner.  When those expectations fail you need to delve deeper to find your answer in several different ways.  It often starts with visiting the website of the package you are working with.  This often leads to..

All of these approaches have their pros and cons and I have often found its some combination of these sources that will lead you to your answer.  In my case I have been trying over the past couple of weeks to get Cocos2D working with a UIScrollView container.  This is a requirement for the game we are working on to allow players to get the familiar scrolling movement you see in many iPhone apps.

We tried a number of approaches to get this to work properly…

  1. Cocos window embedded inside a UIScrollView instance
  2. Changing the size of the OpenGL rendering window to render a larger space.  Works but there is a serious performance hit.
  3. A UIScrollView instance overlaid on top of a Cocos window.  If the scroll movements were caught they could be passed onto the Cocos window manually.

Ultimately we chose option #3 and this is the option I will cover details for in this article as it is ultimately the only solution which worked for us properly.

Modifications to the Cocos source code

The first step to get this to work properly is a small modification to the Cocos source code.  The reason being that Cocos will freeze while working with a UIScrollView by default.  Luckily the location to you need to modify is noticeably documented in the Cocos source code.  In Director.m look for this block of comment with code…

//
// If you want to attach the opengl view into UIScrollView
// uncomment this line to prevent 'freezing'. It doesn't work on
// with the Fast Director
//
// [[NSRunLoop currentRunLoop] addTimer:animationTimerforMode:NSRunLoopCommonModes];

Simply uncomment this line of code to get freeze free scrolling.  Leave the lines commented out to see the difference in movement as my description may not make it readily apparent.

EDIT: If you are using Cocos 0.99.4 or later use the following directions provided by one of our helpful community members “drootang” for solving the freezing issue…


I’m using 0.99.4 and it took me a while to figure out that the new hello world uses the following line in applicationDidFinishLaunching:

CC_DIRECTOR_INIT();

This macro (among other things) does the following:

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

You need to force it to set the director type to kCCDirectorTypeNSTimer to prevent cocos2d from “freezing” while scrolling. Either change
the line in the macro, or copy the macro contents to your appDidFinishLaunching method, and change the line above to simply:

[CCDirector setDirectorType:kCCDirectorTypeNSTimer] ;


Using a view controller and a UIScrollView together

Detecting touches in UIScrollView

One of the necessary steps to getting this to work properly would be to detect both touches and swipes in the UIScrollView instance and pass those messages along to the Cocos window.  Researching this in Google revealed the following forum posting which was a huge help.  The solution there is to use a UIViewController where the view in that controller is an instance of UIScrollView.  Here’s what that setup looks like in our codebase…

CocosOverlayViewController.h

#import 
 
@interface CocosOverlayViewController : UIViewController
{
}
@end

CocosOverlayViewController.m

#import "CocosOverlayViewController.h"
#import "CocosOverlayScrollView.h"
 
@implementation CocosOverlayViewController
 
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
  CocosOverlayScrollView *scrollView = [[CocosOverlayScrollView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
 
  // NOTE - I have hardcoded the size to 1024x1024 as that is the size of the levels in
  // our game.  Ideally this value would be parameterized or configurable.
  //
  scrollView.contentSize = CGSizeMake(1024, 1024);
 
  scrollView.delegate = scrollView;
  [scrollView setUserInteractionEnabled:TRUE];
  [scrollView setScrollEnabled:TRUE];
 
  self.view = scrollView;
 
  [scrollView release];
}
@end

CocosOverlayScrollView.h

#import 
 
@interface CocosOverlayScrollView : UIScrollView
{
}
@end

CocosOverlayScrollView.m

#import "CocosOverlayScrollView.h"
#import "cocos2d.h"
 
@implementation CocosOverlayScrollView
 
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
  if (!self.dragging)
  {
    UITouch* touch = [[touches allObjects] objectAtIndex:0];
    CGPoint location = [touch locationInView: [touch view]];
 
    [self.nextResponder touchesBegan: touches withEvent:event];
    [[[Director sharedDirector] openGLView] touchesBegan:touches withEvent:event];
  }
 
  [super touchesBegan: touches withEvent: event];
}
 
-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
  if (!self.dragging)
  {
    [self.nextResponder touchesEnded: touches withEvent:event];
    [[[Director sharedDirector] openGLView] touchesEnded:touches withEvent:event];
  }
 
  [super touchesEnded: touches withEvent: event];
}
 
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
  // TODO - Custom code for handling deceleration of the scroll view
}
 
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
  CGPoint dragPt = [scrollView contentOffset];
 
  Scene* currentScene = [[Director sharedDirector] runningScene];
 
  // Only take the top layer to modify but other layers could be retrieved as well
  //
  Layer* topLayer = (Layer *)[currentScene.children objectAtIndex:0];
 
  dragPt = [[Director sharedDirector] convertCoordinate:dragPt];
 
  dragPt.y = dragPt.y * -1;
  dragPt.x = dragPt.x * -1;
 
  CGPoint newLayerPosition = CGPointMake(dragPt.x + (scrollView.contentSize.height * 0.5f), dragPt.y + (scrollView.contentSize.width * 0.5f));
 
  [topLayer setPosition:newLayerPosition];
}
 
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
  CGPoint dragPt = [scrollView contentOffset];
 
}
@end

This is by no means a perfect solution yet as I still have some values hardcoded such as the size of the content for the scroll view window. I will certainly be working to improve the finer points but hopefully the core issues I’ve put together will be useful to some of you.  Our next dev video (when we get it posted) will show the scroll view in action.

Improving the Performance of Animating Sprites in cocos2d

Aug 5, 2009   //   by Derek van Vliet   //   Development  //  11 Comments

cocos2d has proven to be invaluable in enabling us to get our iPhone game up and running fast. But when we started playing with animated sprites, we noticed some performance problems. If you are like us and have many sprites that have to have repeating animations, you may have noticed the same thing.

Conventional wisdom (aka the sample code) says that you would create a RepeatForever action using an Animate action to animate an AtlasSprite. When we tried this, however, we noticed that at the end of each animation cycle, the action would deallocate an AtlasSpriteFrame. This is a by-product of the Animate action being able to restore the original frame of animation once the animation has finished playing. This is also what was causing the performance issues.

To resolve this, we created a cut-down version of the Animate action called FastAnimate. It is very similar to the Animate action with the exception that it does not support restoring the original frame of animation. From what we’ve seen, this simple change greatly increases the performance of animating sprites in cocos2d.

FastAnimate.h:

#import "cocos2d.h"
 
@interface FastAnimate : IntervalAction <NSCopying>
{
	Animation *animation;
}
+(id) actionWithAnimation:(id<CocosAnimation>) a;
-(id) initWithAnimation:(id<CocosAnimation>) a;
@end

FastAnimate.m:

#import "FastAnimate.h"
 
@implementation FastAnimate
 
+(id) actionWithAnimation: (id<CocosAnimation>)anim {
	return [[[self alloc] initWithAnimation:anim] autorelease];
}
 
-(id) initWithAnimation: (id<CocosAnimation>)anim {
	NSAssert( anim!=nil, @"Animate: argument Animation must be non-nil");
 
	if( (self=[super initWithDuration: [[anim frames] count] * [anim delay]]) ) {
 
		animation = [anim retain];
	}
	return self;
}
 
-(id) copyWithZone: (NSZone*) zone {
	return [[[self class] allocWithZone: zone] initWithAnimation: animation];
}
 
-(void) dealloc {
	[animation release];
	[super dealloc];
}
 
-(void) startWithTarget:(id)aTarget {
	[super startWithTarget:aTarget];
}
 
-(void) stop {
	[super stop];
}
 
-(void) update: (ccTime) t {
	NSUInteger idx=0;
 
	ccTime slice = 1.0f / [[animation frames] count];
 
	if(t !=0 )
		idx = t/ slice;
 
	if( idx >= [[animation frames] count] ) {
		idx = [[animation frames] count] -1;
	}
	id<CocosNodeFrames> sprite = (id<CocosNodeFrames>) target;
	if (! [sprite isFrameDisplayed: [[animation frames] objectAtIndex: idx]] ) {
		[sprite setDisplayFrame: [[animation frames] objectAtIndex:idx]];
	}
}
@end

cocos2d for iPhone Xcode Project Template

Jul 24, 2009   //   by Derek van Vliet   //   Development  //  4 Comments

Wouldn’t it be nice if you could fire up Xcode and create a new cocos2d iPhone project using the New Project wizard? Thanks to these instructions, you can do just that. This was easily the most useful thing I came across today.

The following assumes you have git installed, which you can download here.

Determine your Xcode root directory. For example, I have Xcode for iPhone OS 2.0 in /Developer and Xcode for iPhone OS 3.0 in /Developer3.0

To set up this project template, add it to: /Developer3.0/Library/Xcode/Project Templates/Other by creating a sub-directory there, and then, from within the sub-directory, get the code with:

git clone git://github.com/tjweir/cocos2d-application.git

If you haven’t already, get the latest cocos2d tarball from http://www.cocos2d-iphone.org/download and untar it.

Under the global Xcode preferences, add (or update if it already exists) a Source Tree called COCOS2D_SRC. Make it point to the top-most cocos2d directory, like /Developer/Library/cocos2d-iphone-0.8-beta/

You should now be able to create a new cocos2d project from this template (it should be somewhere at the bottom of the list when you go to File -> New Project).

Static linking is awesome.

It sure is! Here’s the end result:

cocos2dprojecttemplate

Our Games

Latest Tweets