Chipmunk Tile Tutorial:

This tutorial shows you how you can use Chipmunk Pro in a top down game. It covers an easy way to set up controls and an easy way to generate the collision shape of a tilemap.

Ray Wenderlich's How to Make a Tile-Based Game with Cocos2d is a starting point for this tutorial. We'll be building off that example by adding physics, smooth motion between tiles (the player doesn't snap to a tile), and collisions. If you find this tutorial moves a little too quickly, start with that one first. This example has been updated for Cocos2D 2.1.

You should know the basics about Chipmunk such as how to create spaces, bodies and shapes. You might want to read up on some of the documentation or the Chipmunk Basics tutorial on the documentation page.

You can download this tutorial and all the project files from the GitHub page. These are ready to build!

What Do You Need to Know First?

This tutorial assumes you have some prior experience with Cocos2D development. It doesn't go into how Cocos2D itself works, although the project is pretty simple. We also assume you know how to use Tiled and a few other things covered in this tutorial by Ray Wenderlich.

What is Chipmunk?

Chipmunk is a 2D rigid body physics library distributed under the MIT license. It is intended to be fast, portable, numerically stable, and easy to use. For this reason it's been used in hundreds of games on just about every system you could name. This includes a lot of successful games such as Waking Mars, Night Sky, Zombie Smash, Feed Me Oil and many others. I've put thousands of hours of work over many years to make Chipmunk what it is today. Check out Chipmunk's website for more information.

What is Objective-Chipmunk and Chipmunk Pro?

Objective-Chipmunk is an Objective-C wrapper for the Chipmunk Physics Library distributed as part of Chipmunk Pro and Chipmunk Indie. While Chipmunk's C API is pretty easy to use, the Objective-C API is even better. The primary advantages of a native Objective-C API include integrating with the Cocoa memory management model (including ARC) and the Chipmunk Object protocol. The Chipmunk Object protocol unifies the basic Chipmunk types as well as making it easy to create custom composite collections of the basic types. Additionally, the wrapper adds many convenience methods for doing common setup tasks as well as helper methods that integrate it with the rest of the Cocoa Touch API. The wrapper tries to do things the Objective-C way, adding useful method variations where it makes sense to do so.

You can find out more information on Chipmunk's website. While Objective-Chipmunk is not free like Chipmunk is, the enhanced API will almost certainly save you time and money. You'll also be helping to support further Chipmunk development!

Let's get started!

At the end of Ray's tutorial, you can load a map created in Tiled. Your player can move around, but he snaps to individual tiles, so the game doesn't feel very smooth. First, take a look at the interface for our class. It hasn't changed much from the previous tutorial.

// HelloWorldLayer
@interface HelloWorldLayer : CCLayer
{
    // Inside the HelloWorld class declaration
    CCTMXTiledMap *_tileMap;
    CCTMXLayer *_background;
    
    CCTMXLayer *_meta;
    
    CCPhysicsSprite *_player;
}

The CCTMX classes are used for reading the tile map. Remember, the _background layer contains the tile artwork, such as the desert, the cacti, the roads, and the walls. The _meta layer contains hand-painted collision information. Of course, there are other approaches that might work better in your game, but we're going to roll with this approach.

CCPhysicsSprite *_player

CCPhysicsSprite is a new class in Cocos2d 2.1. Developers often struggle with making their graphics line up with a physics simulation, and this class is very helpful in that regard. Each time the physics simulation updates the position or rotation of an object in the world, the sprite is updated to reflect that. It is a subclass of CCSprite; if you are familiar with that class, the use is similar.

Starting with init:

I'll move quickly through this review:

// on "init" you need to initialize your instance
-(id) init
{
    // always call "super" init
    // Apple recommends to re-assign "self" with the "super" return value
    if( (self=[super init])) {
        
        // Setup the space. We won't set a gravity vector since this is top-down
        space = [[ChipmunkSpace alloc] init];
        
        isTouching = false;
        self.touchEnabled = YES;
        
        _tileMap = [CCTMXTiledMap tiledMapWithTMXFile:@"TileMap.tmx"];
        _background = [_tileMap layerNamed:@"Background"];
        
        [self addChild:_tileMap z:-1];

Here we have basic setup of this Cocos2d layer. This includes stuff like enabling touches, loading the tilemap, and adding it to the layer. Confused? Start with this tutorial.

        
        CCTMXObjectGroup *objects = [_tileMap objectGroupNamed:@"Objects"];
        NSAssert(objects != nil, @"'Objects' object group not found");
        NSMutableDictionary *spawnPoint = [objects objectNamed:@"SpawnPoint"];
        NSAssert(spawnPoint != nil, @"SpawnPoint object not found");
        int x = [[spawnPoint valueForKey:@"x"] intValue];
        int y = [[spawnPoint valueForKey:@"y"] intValue];
        
        _meta = [_tileMap layerNamed:@"Meta"];
        _meta.visible = NO;
        

Tiled supports annotating the map with specific information. So we've done so with a "SpawnPoint" object. We pull out the x and y values for use later. Meta is the name of a layer that controls collision information. Tiles painted in this layer will be solid for our collisions.

        // Add a CCPhysicsDebugNode to draw the space.
        CCPhysicsDebugNode *debugNode = [CCPhysicsDebugNode debugNodeForChipmunkSpace:space];
        [self addChild:debugNode];
        

One nice thing about working with Chipmunk in Cocos2d is easy debugging of shapes with CCPhysicsDebugNode. This node draws all the objects in your physics space to the scene. So if you've got graphics that don't line up with your simulation, or you've forgotten to place a wall, you can easily determine what is going on. These two lines add that debug drawing.

        // We've broken out the creation of the player and terrain into separate methods
        [self createPlayer:y x:x];
        [self createTerrainGeometry];
        
        // add some crates, it's not a video game without crates!
        for(int i=0; i<16; i++){
            float dist = 50.0f;
            [self makeBoxAtX: x + (i % 4) * dist + 200 y: y + ( i / 4) * dist - 200];
        }
        
        [self setViewpointCenter:_player.position];
        
        // schedule updates, which also steps the physics space:
        [self scheduleUpdate];
    }
    

We do three things here: we create the player, create the terrain geometry, then add some boxes. After that there's a little cleanup by making sure the view starts centered on the player and that the Cocos2d update method is scheduled to be called. Let's look at each of these individually.

Creating a Top Down Player using Chipmunk:

Our goal is to have a player that smoothly moves through the world, slides along walls without getting stuck, and can push around blocks. From the perspective of controls, we also want them to quickly and smoothly start and stop moving. So let's get to the code!

- (void)createPlayer:(int)y x:(int)x
{
    // set up the player body and shape
    float playerMass = 1.0f;
    float playerRadius = 13.0f;
    
    playerBody = [space add:[ChipmunkBody bodyWithMass:playerMass andMoment:INFINITY]];
    
    _player = [CCPhysicsSprite spriteWithFile:@"chipmunkMan.png"];
    _player.chipmunkBody = playerBody;
    playerBody.pos = ccp(x,y);
    
    [self addChild:_player];

First, we set up the ChipmunkBody for the player. We create it with a mass of 1.0 and an infinite moment of inertia. The moment of inertia is like mass for how hard something is to rotate. By using an infinite moment, it will prevent the player from rotating ever as no finite force could make it happen. If the player could rotate, then it would start spinning when you slide against walls. And when a rotating player came in contact with something they could push, like a box, the friction between the player and the box would cause it to move in a weird way.

_player is our CCSprite, in this case, a CCPhysicsSprite. CCPhysicsSprite is a subclass of CCSprite that ties the sprite to a physics body. When the physics body moves around, the sprite also moves too. In fact what happens behind the scene is that the sprite just uses the body's position and rotation instead of its own. We set the body of the physics sprite to the ChipmunkBody we just made, and then set the position to (x, y), the position of the spawn point we passed in as an argument.

    
    ChipmunkShape *playerShape = [space add:[ChipmunkCircleShape circleWithBody:playerBody radius:playerRadius offset:cpvzero]];
    playerShape.friction = 0.1;
    

Circles make good representations of just about any character. Before you think you need a more complicated polygon, start with a circle as there are several advantages. Most importantly, circles have no edges or corners that get caught on other geometry. Since they have a constant radius, you are always sure of the size of the player and what they can fit into. We set a little friction, mostly to dictate how our player interacts with other objects.

Although we're thinking of this as a top-down game, the friction component does not represent the player's friction with the ground. There isn't a 'ground' object in our game- you're just always in contact with the ground. We'll get to this more later.

    targetPointBody = [ChipmunkBody bodyWithMass:INFINITY andMoment:INFINITY];
    targetPointBody.pos = ccp(x,y); // make the player's target destination start at the same place the player.

We create a infinite mass body to use as a "control body". This control body represents the target destination of the player. Infinite mass and moment of inertia means this object won't ever move on its own- we'll be moving it ourselves when the player touches the screen. Because this is an iOS demo, we decided to go with a control scheme where you tap a point on the screen, and the character moves towards it. If you hold down your finger, the player keeps moving in that direction. So when it's time to handle input, it's very simple, we just set the position of the control body to the touch.

But how is this static body used to control the motion of the player?

    ChipmunkPivotJoint* joint = [space add:[ChipmunkPivotJoint pivotJointWithBodyA:targetPointBody bodyB:playerBody anchr1:cpvzero anchr2:cpvzero]];
    joint.maxBias = 200.0f;
    joint.maxForce = 3000.0f;

In short, this joint controls the motion of the player by tying together the static targetPointBody and the playerBody. MaxForce controls the player's pushing power, and maxBias controls the correction speed (ie: the speed the player moves across the ground).

I want to know the details!

The Update method:

Our update method is scheduled to run each frame. The first thing we do here is handle the input.

-(void)update:(ccTime)dt
{
    // update player motion based on last touch, if we have our finger down:
    if(isTouching){
        // the screen may have moved, so convert the screen space touch location to one in world (node) space.
        CGPoint touchLocation = [self convertToNodeSpace: [[CCDirector sharedDirector] convertToGL: _lastTouchLocation]];
        targetPointBody.pos = touchLocation;
    }

If we're touching the screen, we want to move the position of targetPointBody to match the touch location, in node space coordinates (meaning the position in the world, not a screen-relative coordinate).

    // Update the physics
    ccTime fixed_dt = [CCDirector sharedDirector].animationInterval;
    [space step:fixed_dt];
    
    //update camera
    [self setViewpointCenter:playerBody.pos];
}

Then we step the physics—this is when the physics engine runs and updates the position of everything in the game. And finally we update the camera so it's centered on the player's current position.

Creating the terrain geometry using Chipmunk's Autogeometry features:

Let's start with a brief rundown on how the you can write different samplers for Chipmunk's auto-geometry feature. Samplers are used by Chipmunk to create geometry by looking at specific points; you could think of it as a density function. A sampling function takes a point (a cpVect) as an argument, and returns the density at that location. Image samplers are an easy example of a sampling function. The sampling function would just look at the image at (x, y) and return the color or alpha of the image at that pixel.

Sampling images is common because it's easy, but you could sample a noise function, a fractal pattern, or any number of things. Here we're going to sample the tilemap.

- (void)createTerrainGeometry
{
    int tileCountW = _meta.layerSize.width;
    int tileCountH = _meta.layerSize.height;
        
    cpBB sampleRect = cpBBNew(-0.5, -0.5, tileCountW + 0.5, tileCountH + 0.5);

We'll be using the cpBB sampleRect later to tell the autogeometry the range that it should look at. It goes from approximately (0, 0) to (tileCountW, tileCountH)... the width and height of the tilemap. I know you already want to know what's up with the 0.5f. There are two things we're doing here:

First, we're sampling from the center of tiles. (0, 0) is the top left corner of our top left tile. (1, 0) is the top left corner of the next tile to the right. If you sampled at (1, 1), it's the corner between 4 tiles. What would the terrain's density be there? Also, the geometry will show up between the samples, and for that to line up with the tile geometry we have to sample between the edges, or at the centers of the tiles.

But we start at (-0.5, -0.5)! Yup. If we started at 0.5, there'd be half a tile gap in our geometry around the edge of the screen. Even if a wall went all the way to the edge of the screen, we could push a narrow box between the wall and the edge. So we want to sample an extra tile out, and then we'll clamp the data in the next step.

    // Create a sampler using a block that samples the tilemap in tile coordinates.
    ChipmunkBlockSampler *sampler = [[ChipmunkBlockSampler alloc] initWithBlock:^(cpVect point){
        

ChipmunkBlockSampler is a convenient way of using an Objective-C block, as noted by the ^. The block has an argument cpVect point and should return a float.

       // Clamp the point so that samples outside the tilemap bounds will sample the edges.
        point = cpBBClampVect(cpBBNew(0.5, 0.5, tileCountW - 0.5, tileCountH - 0.5), point);

First, we clamp the point vector to deal with the border of the map. In the event the the sampler asks for coordinates outside of the tilemap, we want a reasonable response. Clamping ranges outside of the bounds of the tilemap onto the edge is reasonable. We could also just return 1.0 for anything outside of the bounds, or we could wrap the coordinates.

       // The samples will always be at tile centers.
        // So we just need to truncate to an integer to convert to tile coordinates.
        int x = point.x;
        int y = point.y;
        
        // Flip the y-coord (Cocos2D tilemap coords are flipped this way)
        y = tileCountH - 1 - y;
        
        // Look up the tile to see if we set a Collidable property in the Tileset meta layer
        NSDictionary *properties = [_tileMap propertiesForGID:[_meta tileGIDAt:ccp(x, y)]];
        BOOL collidable = [[properties valueForKey:@"Collidable"] isEqualToString:@"True"];
        
        // If the tile is collidable, return a density of 1.0 (meaning solid)
        // Otherwise return a density of 0.0 meaning completely open.
        return (collidable ? 1.0f : 0.0f);
    }];

This is the rest of the sampling function! First, we truncate our sampling points; the tile centered at (3.5f, 2.5f) is stored in our tilemap just as (3, 2). So after we flip things upside down (no one ever agrees if y=0 is on the top or bottom...) we look up the tile in the _meta layer to see if we have marked it "Collidable". This is how we painted our tiles in the Tiled editor. Finally, if it's collidable, we return 1.0, otherwise we return 0.0.

   ChipmunkPolylineSet * polylines = [sampler march:sampleRect xSamples:tileCountH + 2 ySamples:tileCountH + 2 hard:TRUE];

Here's where the magic happens! The march function on the sampler we created takes the sampleRect as an argument. It will run it's algorithm on each tile in the sampleRect. It will do xSamples across the x-axis, and because the sampleRect is tileCountW + 2 tiles wide (remember we added one on the left and right edge), each sample will fall right in the center of each tile. Same for the y-axis.

Let's review with a diagram!

The dark and light background squares represent unwalkable and walkable tiles in our tilemap. The red circles show where we are sampling— the middle of the tiles. The sampler examines 4 points at a time when it creates geometry. So let's examine the four points connected by red dotted lines. Since it detects a transition from a walkable tile to a non-walkable tile, it places geometry halfway between the two points. The hard marching mode always places geometry halfway between points and also axis-aligns the geometry. This is exactly what we want in a tile-based game. Non-hard mode would round the edges.

Also note how there's no geometry on the outer half tile of the example. That's why we do extra work to clamp extra geometry around the border, making sure our collision geometry extends an extra tile to the edge of the map.

Back to business.

    cpFloat tileW = _tileMap.tileSize.width;
    cpFloat tileH = _tileMap.tileSize.height;
    for(ChipmunkPolyline * line in polylines){

And our work is almost over. Our march function returns a ChipmunkPolylineSet. So we loop over each polyline in that set, and each polyline is just a list of connected line segments.

      ChipmunkPolyline * simplified = [line simplifyCurves:0.0f];
 

Then we use the simplifyCurves method to remove redundant points. Since we supply 0.0f as an argument, there's no "fuzz" factor. The lines are removed losslessly, with no changes in geometry. Because the initial polyline contains every segment created by the marching squares algorithm, we've got a lot of short segments that this will collapse into longer ones.

        for(int i=0; i<simplified.count-1; i++){
            
            // The sampler coordinates were in tile coordinates.
            // Convert them to pixel coordinates by multiplying by the tile size.
            cpFloat tileSize = tileH; // fortunately our tiles are square, otherwise we'd need to multiply components independently
            cpVect a = cpvmult(simplified.verts[  i], tileSize);
            cpVect b = cpvmult(simplified.verts[i+1], tileSize);
            
            // Add the shape and set some properties.
            ChipmunkShape *seg = [space add:[ChipmunkSegmentShape segmentWithBody:space.staticBody from:a to:b radius:1.0f]];
            seg.friction = 1.0;
        }
    }

Now we loop over each segment in our simplified line loop, and convert it's coordinates into pixel coordinates. Then we create and add a ChipmunkSegment just like normal.

Add some boxes!

Well, the hard part is over. Now we want to have something to push around.


- (ChipmunkBody*)makeBoxAtX:(int)x y:(int)y
{
    float mass = 0.3f;
    float size = 27.0f;
    
    ChipmunkBody* body = [ChipmunkBody bodyWithMass:mass andMoment:cpMomentForBox(mass, size, size)];
    
    CCPhysicsSprite * boxSprite = [CCPhysicsSprite spriteWithFile:@"crate.png"];
    boxSprite.chipmunkBody = body;
    boxSprite.position = cpv(x,y);
    
    ChipmunkShape* boxShape = [ChipmunkPolyShape boxWithBody:body width: size height: size];
    boxShape.friction = 1.0f;
    
    [space add:boxShape];
    [space add:body];
    [self addChild:boxSprite];

Much of this is similar to how we created our player. We create a ChipmunkBody with the mass and size that we've picked out. We've got our convenient CCPhysicsSprite created. [ChipmunkPolyShape boxWithBody] is a shortcut to creating a box shape- a rectangular polygon. Then we add the shape and body to the to the space, and add the sprite to the layer.

    ChipmunkPivotJoint* pj = [space add: [ChipmunkPivotJoint pivotJointWithBodyA:
                                          [space staticBody] bodyB:body anchr1:cpvzero anchr2:cpvzero]];
    
    pj.maxForce = 1000.0f; // emulate linear friction
    pj.maxBias = 0; // disable joint correction, don't pull it towards the anchor.
    
    // Then use a gear to fake an angular friction (slow rotating boxes)
    ChipmunkGearJoint* gj = [space add: [ChipmunkGearJoint gearJointWithBodyA:[space staticBody] bodyB:body phase:0.0f ratio:1.0f]];
    
    gj.maxForce = 5000.0f;
    gj.maxBias = 0.0f;
    return body;
}

Since this is a top-down game, we need to fake friction with the "ground" that things are standing on. We do this by creating two joints, one for linear friction and one for angular friction.

Linear friction is basically what we want to simulate to make something that is moving in a straight line slow down. The maxForce basically controls how quickly the object slows down- how much force can be applied each frame until it reaches the same speed as the static body (which is zero). We set the maxBias, which we discussed briefly above. Recall that maxBias limits the maximum speed at which a joint can be corrected. For this joint, "correcting the joint" at a certain maxBias means how quickly Chipmunk will try to make the anchors move back to overlap. Because we've set the maxBias to zero, it doesn't try to correct these positions, and it ignores the fact that you are stretching the joint out. It only operates on the velocity.

Angular friction is simulated to make a rotating object slowly stop rotating. Gear joints govern rotation, so we've chosen to use that. Here we set the maxBias and maxForce just like above. By using a gear joint with a gear ratio of 1.0, you are asking Chipmunk to try and make the objects spin at the same speed. In this case, just 0.

In this example, we're not using the ChipmunkObject protocol, which can simplify adding physics objects, especially ones more complicated than our boxes. Once you get this tutorial working, you should look into it for your game. It's covered in the Chipmunk Color Match Tutorial.

Conclusion

That's it! We've covered a few advanced topics, like good ways to deal with friction in a top down game, and how to use Chipmunk's Autogeometry code in a tile based game. If you have questions, comments, or suggestions regarding this tutorial, feel free to email us at admin at howlingmoonsoftware.com. Also consider checking out the Chipmunk Forum.