How To Create A Game Like Tiny Wings with Cocos2D 2.X Part 2

Ali Hafizji
Make a game like Tiny Wings!

Make a game like Tiny Wings!

Update 5/19/2013 Fully updated for Cocos2D 2.X. (original post by Ray Wenderlich, update by Ali Hafizji).

This is the second part of a tutorial series that shows you how to make a game like the popular Tiny Wings game by Andreas Illiger!

In the prerequisite for this tutorial series, I first covered how to create dynamic background textures for the game.

In the first part of the tutorial series, I covered how to create the dynamic hills that you need in the game.

In this second and final part of the series, you’ll finally get to the fun stuff – how to add the main character to the game, and use Box2D to simulate his movement!

As a reminder, this tutorial is based on a great demo project written by Sergey Tikhonov – so special thanks again to Sergey!

This tutorial assumes you are familiar with Cocos2D and Box2D. If you are new to Cocos2D or Box2D, you should check out some of the other Cocos2D and Box2D tutorials on this site first.

Getting Started

If you don’t have it already, download a copy of the sample project where you left it off in the previous tutorial.

Next, you’re going to add what I like to think of as the “Hello, Box2D!” code. You’ll create a Box2D world and add some code to set up debug drawing and add some test shapes, just to make sure you have everything working.

Start by making the following modifications to

// Add to top of file
#import "Box2D.h"
// Add inside @interface
b2World * _world;

Also add the following constant to the Prefix.pch file:

#define PTM_RATIO   32.0

This adds the Box2D header and the debug draw header file. It also declares the pixel-to-meter ratio (PTM_RATIO) to 32.0. As a refresher, this is what you need to use to convert between Box2D units (meters) and Cocos2D units (points).

Then add the following new methods to, above the onEnter method:

- (void)setupWorld {
    b2Vec2 gravity = b2Vec2(0.0f, -7.0f);
    bool doSleep = true;
    _world = new b2World(gravity);
- (void)createTestBodyAtPostition:(CGPoint)position {
    b2BodyDef testBodyDef;
    testBodyDef.type = b2_dynamicBody;
    testBodyDef.position.Set(position.x/PTM_RATIO, position.y/PTM_RATIO);
    b2Body * testBody = _world->CreateBody(&testBodyDef);
    b2CircleShape testBodyShape;
    b2FixtureDef testFixtureDef;
    testBodyShape.m_radius = 25.0/PTM_RATIO;
    testFixtureDef.shape = &testBodyShape;
    testFixtureDef.density = 1.0;
    testFixtureDef.friction = 0.2;
    testFixtureDef.restitution = 0.5;

If you’re familiar with Box2D, these methods should be review.

The setupWorld method creates a Box2D world with a certain amount of gravity – a little less than “Earth standard” of -9.8 m/s².

The createTestBodyAtPostition: method creates a test object – a circle with a 25 point radius. You’ll use this to create a test object wherever you tap for testing purposes.

You’re not done with yet – make a few more modifications to it now:

// Add to the TOP of onEnter
[self setupWorld];
// Replace line to create Terrain in onEnter with the following
_terrain = [[[Terrain alloc] initWithWorld:_world] autorelease];
// Add to the TOP of update
static double UPDATE_INTERVAL = 1.0f/60.0f;
static double MAX_CYCLES_PER_FRAME = 5;
static double timeAccumulator = 0;
timeAccumulator += dt;    
if (timeAccumulator > (MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL)) {
    timeAccumulator = UPDATE_INTERVAL;
int32 velocityIterations = 3;
int32 positionIterations = 2;
while (timeAccumulator >= UPDATE_INTERVAL) {        
    timeAccumulator -= UPDATE_INTERVAL;        
                 velocityIterations, positionIterations);        
// Add to bottom of ccTouchesBegan
UITouch *anyTouch = [touches anyObject];
CGPoint touchLocation = [_terrain convertTouchToNodeSpace:anyTouch];
[self createTestBodyAtPostition:touchLocation];

The code you added to onEnter just calls the new method you wrote earlier to set up the Box2D world. You also modified the line to initialize the Terrain class to pass in the Box2D world. This way it can use the world to create a Box2D body for the hill eventually. You’ll write some placeholder code for this next.

The code you added to update gives Box2D time to run its physics simulation each update by calling _world->Step. Note that this is a fixed-timestep implementation, which is better than variable rate timesteps when running physics simulations. For more information on how this works, check out our Cocos2D book‘s Box2D chapters.

The code you added to ccTouchesBegan:withEvent: method runs the helper method to create a Box2D body wherever you tapped. Again, this is just for debugging to verify Box2D is working OK so far.

Notice that this gets the coordinate of the touch within the terrain coordinates. This is because the terrain will be scrolling, and you want to know the position within the terrain, not the position on the screen.

Next, you’re going to make some changes to Terrain.h/m. Make the following changes to Terrain.h:

// Add to top of file
#import "Box2D.h"
// Add after setOffsetX: method
- (id)initWithWorld:(b2World *)world;

This just includes Box2D, and predeclares a new initializer that will take the Box2D world as a parameter.

Next open Terrain.m and make the following changes:

//Add to top of file
#import "GLES-Render.h"
// Add inside @interface
b2World *_world;
b2Body *_body;
GLESDebugDraw * _debugDraw;

Then add the following new method to Terrain.m, above generateHills:

- (void) resetBox2DBody {
    if(_body) return;
    CGPoint p0 = _hillKeyPoints[0];
    CGPoint p1 = _hillKeyPoints[kMaxHillKeyPoints-1];
    b2BodyDef bd;
    bd.position.Set(0, 0);
    _body = _world->CreateBody(&bd);
    b2EdgeShape shape;
    float minY = 0;
    CGSize winSize = [CCDirector sharedDirector].winSize;
    if (winSize.height > 480) {
        minY = (1136 - 1024)/4;
    b2Vec2 ep1 = b2Vec2(p0.x/PTM_RATIO, minY/PTM_RATIO);
    b2Vec2 ep2 = b2Vec2(p1.x/PTM_RATIO, minY/PTM_RATIO);    
    shape.Set(ep1, ep2);
    _body->CreateFixture(&shape, 0);

This is just a helper method that creates a Box2D body along the bottom of the hill, to represent the “ground.” This is just a temporary method so we can add bodies and have them hit something rather than falling endlessly, you’ll replace this later to model the hill itself.

For now, all it does is figure out the x-coordinate for the first and last keypoints, and draw an edge connecting the two.

Next, add a few more modifications to Terrain.m in order to call this code, and set up debug drawing:

// Add inside resetHillVertices, right after "prevToKeyPointI = _toKeyPointI" line:
[self resetBox2DBody];
// Add new method above init
- (void)setupDebugDraw {
    _debugDraw = new GLESDebugDraw(PTM_RATIO);
    _debugDraw->SetFlags(GLESDebugDraw::e_shapeBit | GLESDebugDraw::e_jointBit);
// Replace init with the following
- (id)initWithWorld:(b2World *)world {
    if ((self = [super init])) {
        _world = world;
        [self setupDebugDraw];
        [self generateHills];
        [self resetHillVertices];
        self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture];
    return self;
// Add at bottom of draw

Every time the hill vertices are reset, you call resetBox2DBody to create the Box2D representation of the visible hills. Right now the body never changes (it’s just adding a line for the ground) but later on you’ll fix up the Box2D body to model the visible hills.

setupDebugDraw and the draw code is what you need to set up debug drawing for the Box2D objects. If you’re familiar with Box2D, this should be review.

However, you may be wondering why the debug draw code is in Terrain.m, instead of in This is because scrolling is implemented in this game by moving Terrain.m. So to match up the coordinate system of Box2D with what’s visible on the screen, we need to add the debug drawing code into Terrain.m.

One last step. If you try to compile right now, you’ll get a ton of errors. This is because Terrain.m imports Terrain.h, which imports Box2D.h. And whenever you have a .m file import C++ code (like Box2D) you get a ton of errors.

Luckily the solution is simple – renamed Terrain.m to

Compile and run, and now as you tap you can see a lot of circle objects fall into your scene!
Hills with ground and Box2D objects

Shaping the Hills in Box2D

Right now you have a Box2D shape representing the ground along the bottom of the screen, but what you really want is a shape representing the hills.

Luckily, this is quite easy since you already have all of the pieces in place!

  • You have an array of vertices of the top of the hill (borderVertices). You created this in the last tutorial when you generated the triangle strip in resetHillVertices.
  • You have a method that is called whenever the vertices change (resetBox2DBody).

So you just need to replace resetBox2DBody to create edges for each entry in borderVertices! Replace it with the following code:

- (void) resetBox2DBody {
    if(_body) {
    b2BodyDef bd;
    bd.position.Set(0, 0);
    _body = _world->CreateBody(&bd);
    b2EdgeShape shape;
    b2Vec2 p1, p2;
    for (int i=0; i<_nBorderVertices-1; i++) {
        p1 = b2Vec2(_borderVertices[i].x/PTM_RATIO,_borderVertices[i].y/PTM_RATIO);
        p2 = b2Vec2(_borderVertices[i+1].x/PTM_RATIO,_borderVertices[i+1].y/PTM_RATIO);
        shape.Set(p1, p2);
        _body->CreateFixture(&shape, 0);

This new implementation first looks to see if there’s already a Box2D body, and destroys the old one first before continuing.

Then it creates a new body, and starts looping through the array of border vertices, which contain the vertices for the top of the hill. For each piece, it creates an edge connecting the two.

Pretty easy, eh? Compile and run, and now you’ll have a nice Box2D body following the slope of your hills!

Box2D hills with slopes made via edges

Adding the Seal

All this time you’ve been working with a project named Tiny Seal, but there’s no seal!

That is just a tragedy, so let’s address that right away.

First, download and unzip the resources file for this project, and drag the “Sprite Sheets” and “Sounds” directories into your Xcode project. For each directory, verify “Copy items into destination group’s folder” is selected, and click Finish.

Go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter Hero for Class, CCSprite for Subclass, and click Next, then Create.

Then rename Hero.m to (remember you need to use the .mm extension since this file will use Box2D).

Replace Hero.h with the following:

#import "Box2D.h"
@interface Hero : CCSprite
- (id)initWithWorld:(b2World *)world;
- (void)update;

This is pretty simple stuff – just imports Box2D.h and declares methods.

Then switch to and replace it with the following:

#import "Hero.h"
#import "HelloWorldLayer.h"
@interface Hero() {
    b2World *_world;
    b2Body *_body;
    BOOL _awake;
@implementation Hero
- (void)createBody {
    float radius = 16.0f;
    CGSize size = [[CCDirector sharedDirector] winSize];
    int screenH = size.height;
    CGPoint startPosition = ccp(0, screenH/2+radius);
    b2BodyDef bd;
    bd.type = b2_dynamicBody;
    bd.linearDamping = 0.1f;
    bd.fixedRotation = true;
    bd.position.Set(startPosition.x/PTM_RATIO, startPosition.y/PTM_RATIO);
    _body = _world->CreateBody(&bd);
    b2CircleShape shape;
    shape.m_radius = radius/PTM_RATIO;
    b2FixtureDef fd;
    fd.shape = &shape;
    fd.density = 1.0f / CC_CONTENT_SCALE_FACTOR();
    fd.restitution = 0.0f;
    fd.friction = 0.2;
- (id)initWithWorld:(b2World *)world {
    if ((self = [super initWithSpriteFrameName:@"seal1.png"])) {
        _world = world;
        [self createBody];
    return self;
- (void)update {
    self.position = ccp(_body->GetPosition().x*PTM_RATIO, _body->GetPosition().y*PTM_RATIO);
    b2Vec2 vel = _body->GetLinearVelocity();
    b2Vec2 weightedVel = vel;
    float angle = ccpToAngle(ccp(vel.x, vel.y));  
    if (_awake) {  
        self.rotation = -1 * CC_RADIANS_TO_DEGREES(angle);

The createBody method creates a circle shape representing the seal. This is almost exactly the same as the createTestBodyAtPosition method you wrote earlier, except it bases the radius on the seal size (actually smaller than the seal, for the collisions to work right).

Also, the friction is set to 0.2 (so the seal is extremely slippery) and the restitution is set to 0 (so the seal doesn’t bounce when he hits the ground).

It also sets some linear damping on the body so it tends to slow down over time, and sets the body to fixed rotation since it doesn’t really need to rotate for this game.

The initWithWorld: method initializes the sprite to a particular sprite frame (seal1.png), squirrels away a copy of the world, and calls the above createBody method.

The update method updates the position of the seal sprite to match the position of the Box2D body, and the rotation of the sprite based on the seal’s velocity.

Next, you need to make a few modifications to Terrain.h and, because you’re going to add a sprite batch node inside Terrain.m.

Start by making the following modifications to Terrain.h:

// Inside @interface after the variable declaration block
@property (retain) CCSpriteBatchNode * batchNode;

Then switch to the file and make the following modifications:

// Add at bottom of initWithWorld:
_batchNode = [CCSpriteBatchNode batchNodeWithFile:@"TinySeal.png"];
[self addChild:_batchNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"TinySeal.plist"];

This simply creates a batch node for the TinySeal.png sprite sheet, and loads the sprite frame definitions from TinySeal.plist into the sprite frame cache.

Almost done! Just make the following modifications to

// Add to top of file
#import "Hero.h"
// Add inside @interface
Hero * _hero;
// Add to bottom of onEnter
_hero = [[[Hero alloc] initWithWorld:_world] autorelease];
[_terrain.batchNode addChild:_hero];
// In update, comment out the three lines starting with PIXELS_PER_SECOND and add the following
[_hero update];
float offset = _hero.position.x;

Compile and run, and you should now see a happy seal on the left side of the screen!

Seal visible, but partially offscreen

But it’s kind of annoying he’s offscreen. It would be better if we showed him more to the right a bit.

Luckily this is easy to fix! Just open up and replace setOffsetX with the following:

- (void) setOffsetX:(float)newOffsetX {
    CGSize winSize = [CCDirector sharedDirector].winSize;
    _offsetX = newOffsetX;
    self.position = CGPointMake(winSize.width/8-_offsetX*self.scale, 0);
    [self resetHillVertices];

This adds one eigth of the winSize.width to the passed-in offset, so the seal appears to the right a bit. Compile and run, and now the seal is more visible!

Offset modified so seal is onscreen

Note: If you’re running this on a retina display, the seal will be half size. This is because we haven’t provided retina-sized artwork – in a real game, you’d obviously provide that :]

Making the Seal Move

You’re getting close to a game at this point – you have a seal, you just need to let him fly!

The strategy you’re going to take is the following:

  • The first time the screen is tapped, make the seal jump up to the right a bit to get him started.
  • Whenever a touch is held down, apply a force to the seal to push him down. This will make him go quickly down hill, giving him velocity that will make him fly up when he goes up the next hill.
  • Add some code to make sure the seal moves at least a set amount. We don’t want the poor seal to get stuck!

Let’s try this out. Make the following modifications to Hero.h:

// Add after @interface block
@property (readonly) BOOL awake;
- (void)wake;
- (void)dive;
- (void)limitVelocity;

Then make the following modifications to

// Add new methods
- (void) wake {
    _awake = YES;
    _body->ApplyLinearImpulse(b2Vec2(1,2), _body->GetPosition());
- (void) dive {
- (void) limitVelocity {    
    if (!_awake) return;
    const float minVelocityX = 5;
    const float minVelocityY = -40;
    b2Vec2 vel = _body->GetLinearVelocity();
    if (vel.x < minVelocityX) {
        vel.x = minVelocityX;
    if (vel.y < minVelocityY) {
        vel.y = minVelocityY;

The wake method applies an impulse to the upper right to get the seal initially moving.

The dive method applies a strong impulse down, and an impulse slightly to the right. The down impulse will cause the seal to collide off of the hill, and the stronger the slope of the hill the more the seal will want to “fly up” once the next hill arrives.

limitVelocity just makes sure the seal is moving at least 5m/s² along the x-axis, and no less than -40m/s² along the y-axis.

Almost done – just a few modifications to HelloWorldLayer. Start with by adding the following instance variable inside the @interface:

BOOL _tapDown;

Then make the following changes to the methods in

// Add at the top of the update method
if (_tapDown) {
    if (!_hero.awake) {
        [_hero wake];
        _tapDown = NO;
    } else {
        [_hero dive];
[_hero limitVelocity];
// Replace ccTouchesBegan with the following
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self genBackground];
    _tapDown = YES;    
// Add new methods
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    _tapDown = NO;    
- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    _tapDown = NO;

Compile and run, and now you should have a seal that can fly through the air!

Your seal can now fly high into the air!

Fixing a Shaky Seal

You may notice when trying out the level that the seal switches rotation in a shaky manner as he slides along the ground.

One way to fix this is to take the weighted average of the past few linear velocities instead of always setting the position on the current velocity.

Let’s try this out. Add the following to

// Add to top of file
#define NUM_PREV_VELS   5
// Add inside @interface
b2Vec2 _prevVels[NUM_PREV_VELS];
int _nextVel;

And modify the update method as follows:

- (void)update {
    self.position = ccp(_body->GetPosition().x*PTM_RATIO, _body->GetPosition().y*PTM_RATIO);
    b2Vec2 vel = _body->GetLinearVelocity();
    b2Vec2 weightedVel = vel;
    for(int i = 0; i < NUM_PREV_VELS; ++i) {
        weightedVel += _prevVels[i];
    weightedVel = b2Vec2(weightedVel.x/NUM_PREV_VELS, weightedVel.y/NUM_PREV_VELS);    
    _prevVels[_nextVel++] = vel;
    if (_nextVel >= NUM_PREV_VELS) _nextVel = 0;
    float angle = ccpToAngle(ccp(weightedVel.x, weightedVel.y));     
    if (_awake) {   
        self.rotation = -1 * CC_RADIANS_TO_DEGREES(angle);

This takes the average of the past 5 velocities instead of always taking just the linear velocity. Compile and run, and now you should see a much smoother seal!

Smoothly rotating seal

Zooming Out

One cool effect that Tiny Wings does is it zooms out the level the higher you go. This not only keeps the hero in view, but it adds to a cool feeling that you’re really moving!

To add this, simply add the following lines after the call to [_hero update] in‘s update method:

CGSize winSize = [CCDirector sharedDirector].winSize;
float scale = (winSize.height*3/4) / _hero.position.y;
if (scale > 1) scale = 1;
_terrain.scale = scale;

If the hero is at winSize.height*3/4, the scale will be 1. If he is < winSize.height*3/4, the scale will increase. If he is > winSize.height*3/4, the scale will decrease, effectively zooming out.

Compile and run, and see how high you can fly!

Zoom-out effect with seal flying high into the air

Gratuitous Animation and Music

You know I couldn’t leave you guys hanging without some gratuitous animation and music!

Will only take a second for a bit more awesomeness! Start by making the following changes to

// Add inside @interface
CCAnimation *_normalAnim;
CCAnimate *_normalAnimate;

Then add the following method declarations in the Hero.h file:

// Add after @interface
- (void)nodive;
- (void)runForceAnimation;
- (void)runNormalAnimation;

This just declares the animation you’ll be creating, and the methods you’ll call whenever the seal is NOT diving.

Next make the following modifications to

// Add new methods
- (void)runNormalAnimation {
    if (_normalAnimate || !_awake) return;
    _normalAnimate = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:_normalAnim]];
    [self runAction:_normalAnimate];
- (void)runForceAnimation {
    [_normalAnimate stop];
    _normalAnimate = nil;
    [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"seal_downhill.png"]];
- (void)nodive {
    [self runNormalAnimation];
// Add at bottom of initWithWorld:
_normalAnim = [[CCAnimation alloc] init];
[_normalAnim addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"seal1.png"]];
[_normalAnim addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"seal2.png"]];
[_normalAnim setDelayPerUnit:0.1];

This creates an animation for when the seal is moving normally, and a method to run that animation. The “animation” for diving is actually just a single frame, so we add a helper method to set that too.

Finally make a few changes to

// At top of file
#import "SimpleAudioEngine.h"
// At end of onEnter
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"TinySeal.caf" loop:YES];
// At start of update, add an else case for the if (_tapDown):
else {
    [_hero nodive];
// Inside ccTouchesBegan
[_hero runForceAnimation];
// Inside ccTouchesEnded AND ccTouchesCancelled
[_hero runNormalAnimation];

Finally go to and comment out the call to _world->DrawDebugData in the draw method.

Compile and run the code, and now your seal can ride the hills in style!

Finished game like Tiny Wings

Where To Go From Here?

Here is the sample code from the above tutorial.

At this point, you have a basic game working. Why not try to tweak the behavior of the seal so he moves in a more natural way up and down the hills? Or start adding in items to pick up, decorations on the hills, points to collect – your imagination is the only limit!

If you extend the game to add any cool new features or have any comments or questions, please join the forum discussion below!

Ali Hafizji

Ali is an independent iOS and Android developer currently focussing on building immersive experiences on mobile devices. He is an avid programmer and loves learning better and faster ways of solving problems. You can follow him on Twitter or github.

Other Items of Interest Weekly

Sign up to receive the latest tutorials from each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Come check out Alt U

Our Books

Our Team

Video Team

... 9 total!

Swift Team

... 15 total!

iOS Team

... 47 total!

Android Team

... 15 total!

OS X Team

... 12 total!

Apple Game Frameworks Team

... 15 total!

Unity Team

... 11 total!

Articles Team

... 8 total!