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!
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.
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.
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!
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.
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 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.
I'll move quickly through this review:
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.
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.
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 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.
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!
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.
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.
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?
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).
Our update method is scheduled to run each frame. The first thing we do here is handle the input.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Well, the hard part is over. Now we want to have something to push around.
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.
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.
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.