Chipmunk Tutorial for iOS: How To Create A Simple iPhone Game

A Chipmunk tutorial that shows you how to create a fun and simple iPhone game called “Cat nap” with realistic physics! By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Decorating with Sprites

If you’ve downloaded the starter project and skipped ahead, here’s where you should pick back up – welcome back!

Now that you’ve got a basic Chipmunk scene in place, let’s get this game started by adding a some actual game art into the scene.

So start by adding the art! Open up the resources for this tutorial and drag the Spritesheets folder into your Xcode project. Make sure “Copy items into destination group’s folder (if needed)” is checked, and click Finish.

If you take a look at the sprite sheet, you’ll see the images you’ll be using in this game:

Sprite sheet for Cat Nap Minigame

First you’ll be creating a Chipmunk body and box shape to represent the cat, as well as the orange and black bricks that block the cat’s way to the bed.

But how do you associate a Chipmunk body with a Cocos2D sprite? It’s simple – you just set the position/angle of the sprite to be the same as the position/angle of the Chipmunk body, each frame.

And how do you keep track of which Chipmunk body a sprite should “follow”? There are several ways to do this, but here’s the approach we’re going to take:

  • Create a subclass of CCSprite called CPSprite (for Chipmunk sprite).
  • Have it keep track of which Chipmunk body the sprite is “following”.
  • Create a method on CPSprite called update, that sets its position/angle to the position/angle of the Chipmunk body.
  • For each CPSprite in the layer, call update each frame.

Let’s see how this works. Go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter CCSprite for “Subclass of”, click Next, name the file CPSprite.m, and click Save.

Then replace CPSprite.h with the following:

#import "cocos2d.h"
#import "chipmunk.h"

@interface CPSprite : CCSprite {
    cpBody *body;
    cpShape *shape;
    cpSpace *space;
    BOOL canBeDestroyed;
}

@property (assign) cpBody *body;

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location spriteFrameName:(NSString *)spriteFrameName;
- (void)update;
- (void)createBodyAtLocation:(CGPoint)location;
- (void)destroy;

@end

First, this imports the headers for Chipmunk and Cocos2D. It then creates instance variables to keep track of the Chipmunk body that it is following, along with the Chipmunk space and shape. It also has a boolean to keep track of whether this object should be able to be destroyed when the user taps on it.

It then defines an initializer for the object, and predeclares the method to update the sprite’s position/rotation based on the Chipmunk body it’s following, the method to create a Chipmunk body/shape based on the sprite’s size, and a method to destroy the Chipmunk body/shape and sprite.

Let’s implement this next. Switch to CPSprite.m and replace it with the following:

#import "CPSprite.h"

@implementation CPSprite
@synthesize body;

- (void)update {    
    self.position = body->p;
    self.rotation = CC_RADIANS_TO_DEGREES(-1 * body->a);
}

- (void)createBodyAtLocation:(CGPoint)location {
    
    float mass = 1.0;
    body = cpBodyNew(mass, cpMomentForBox(mass, self.contentSize.width, self.contentSize.height));
    body->p = location;
    body->data = self;
    cpSpaceAddBody(space, body);
    
    shape = cpBoxShapeNew(body, self.contentSize.width, self.contentSize.height);
    shape->e = 0.3; 
    shape->u = 1.0;
    shape->data = self;
    cpSpaceAddShape(space, shape);
    
}

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location spriteFrameName:(NSString *)spriteFrameName {
    
    if ((self = [super initWithSpriteFrameName:spriteFrameName])) {

        space = theSpace;
        [self createBodyAtLocation:location];
        canBeDestroyed = YES;
        
    }
    return self;
    
}

- (void)destroy {
    
    if (!canBeDestroyed) return;
    
    cpSpaceRemoveBody(space, body);
    cpSpaceRemoveShape(space, shape);
    [self removeFromParentAndCleanup:YES];
}

@end

The update method is very simple. It sets the position of the sprite to be the same as the position of the Chipmunk body. It does the same thing for the rotation, except it has to do a little conversion (because Cocos2D uses degrees rather than radians, and rotations are clockwise rather than counterclockwise).

createBodyAtLocation is very similar to the createBoxAtLocation code you wrote earlier – it first creates a body, then creates a box shape on that body. The main difference is that it sets the size of the box equal to the size of the sprite (the contentSize).

Also, another difference is that it sets the data pointer of the body and shape equal to self (the sprite). This is so that if you have a Chipmunk body or shape, you can get the sprite associated with it through the data pointer, as you’ll see later.

initWithSpace first calls initwithSpriteFrameName on its superclass (CCSprite), then sets up the instance variables and makes the call to create the body.

destroy simply calls cpSpaceRemoveBody and cpSpaceRemoveShape to destroy the body/shape upon request. It also calls removeFromParentAndCleanup to remove the sprite itself.

Now that you have this class in place, you could use it to add the cat sprite and block sprites into the scene just by calling the initializers. But to make things a little bit nicer and more extensible, we’re going to create subclasses for these next.

Subclasses for Sprites

These are very simple so we’re going to go through these quickly. Go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter CPSprite for “Subclass of”, click Next, name the file CatSprite.m, and click Save.

Open CatSprite.h and replace it with the following:

#import "CPSprite.h"

@interface CatSprite : CPSprite {
}

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location;

@end

Then open CatSprite.m and replace it with the following:

#import "CatSprite.h"

@implementation CatSprite

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location {
    if ((self = [super initWithSpace:theSpace location:location spriteFrameName:@"cat_sleepy.png"])) {
        canBeDestroyed = NO;        
    }
    return self;
}

@end

As you can see, not much is going on here except it sets the name of the image to use, and sets canBeDestroyed to be NO.

Now go to File\New\New File again, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter CPSprite for “Subclass of”, click Next, name the file SmallBlockSprite.m, and click Save.

Open up SmallBlockSprite.h and replace it with the following:

#import "CPSprite.h"

@interface SmallBlockSprite : CPSprite {
    
}

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location;

@end

Then replace SmallBlockSprite.m with the following:

#import "SmallBlockSprite.h"

@implementation SmallBlockSprite

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location {
    if ((self = [super initWithSpace:theSpace location:location spriteFrameName:@"block_1.png"])) {
    }
    return self;
}

@end

One last file. Go to File\New\New File again, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter CPSprite for “Subclass of”, click Next, name the file LargeBlockSprite.m, and click Save.

Replace LargeBlockSprite.h with the following:

#import "CPSprite.h"

@interface LargeBlockSprite : CPSprite {
    
}

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location;

@end

Then replace LargeBlockSprite.m with the following:

#import "LargeBlockSprite.h"

@implementation LargeBlockSprite

- (id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location {
    if ((self = [super initWithSpace:theSpace location:location spriteFrameName:@"block_2.png"])) {
    }
    return self;
}

@end

Ok, phew! Now let’s put these to use by creating a few instance of these sprites and adding them to the layer.

Switch to ActionLayer.h and make the following changes:

// Import at top of file
#import "CatSprite.h"
#import "SmallBlockSprite.h"
#import "LargeBlockSprite.h"

// Add inside @interface
CCSpriteBatchNode *batchNode;
CatSprite *cat;

This imports the headers you just made, and adds an instance variable to keep track of the sprite batch node, and one to keep track of the cat sprite.

Then switch to ActionLayer.m and add the following at the end of your init method (right after setting isTouchEnabled = YES):

[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"catnap.plist"];
batchNode = [CCSpriteBatchNode batchNodeWithFile:@"catnap.png"];
[self addChild:batchNode];

cat = [[[CatSprite alloc] initWithSpace:space location:ccp(245, 217)] autorelease];
[batchNode addChild:cat];  

SmallBlockSprite *block1a = [[[SmallBlockSprite alloc] initWithSpace:space location:ccp(213, 47)] autorelease];
[batchNode addChild:block1a];

SmallBlockSprite *block1b = [[[SmallBlockSprite alloc] initWithSpace:space location:ccp(272, 59)] autorelease];
[batchNode addChild:block1b];

SmallBlockSprite *block1c = [[[SmallBlockSprite alloc] initWithSpace:space location:ccp(267, 158)] autorelease];
[batchNode addChild:block1c];

LargeBlockSprite *block2a = [[[LargeBlockSprite alloc] initWithSpace:space location:ccp(270, 102)] autorelease];
[batchNode addChild:block2a];
cpBodySetAngle(block2a.body, CC_DEGREES_TO_RADIANS(90));

LargeBlockSprite *block2b = [[[LargeBlockSprite alloc] initWithSpace:space location:ccp(223, 139)] autorelease];
cpBodySetAngle(block2b.body, CC_DEGREES_TO_RADIANS(90));
[batchNode addChild:block2b];

LargeBlockSprite *block2c = [[[LargeBlockSprite alloc] initWithSpace:space location:ccp(214, 85)] autorelease];
[batchNode addChild:block2c]; 

This creates a sprite batch node with the art from the resources folder, and loads all of the sprite frame info into the cache. It then makes several sprites using the subclasses you just made – which also makes a Chipmunk body/shape for each sprite to “follow” as part of the process.

Of course, to get the sprites to actually follow the Chipmunk bodies, you have to call their update method each frame. So add this to the bottom of your update method:

for (CPSprite *sprite in batchNode.children) {
    [sprite update];
}

Compile and run your project, and you should see your cat sitting across several blocks!

Decorating Chipmunk bodies with Cocos2D Sprites

You can get rid of those extra blocks by commenting out the calls to createBoxAtLocation in your init method too.