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

Learn how to create a game like Tiny Wings with Cocos2D 2.X in this tutorial series – including dynamic terrain generation and game physics! By Ali Hafizji.

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

A Better Hills Algorithm

If you choose to use Sergey's implementation, replace generateHills in Terrain.m with the following:

- (void) generateHills {
    
    CGSize winSize = [CCDirector sharedDirector].winSize;
    
    float minDX = 160;
    float minDY = 60;
    int rangeDX = 80;
    int rangeDY = 40;
    
    float x = -minDX;
    float y = winSize.height/2;
    
    float dy, ny;
    float sign = 1; // +1 - going up, -1 - going  down
    float paddingTop = 20;
    float paddingBottom = 20;
    
    for (int i=0; i<kMaxHillKeyPoints; i++) {
        _hillKeyPoints[i] = CGPointMake(x, y);
        if (i == 0) {
            x = 0;
            y = winSize.height/2;
        } else {
            x += rand()%rangeDX+minDX;
            while(true) {
                dy = rand()%rangeDY+minDY;
                ny = y + dy*sign;
                if(ny < winSize.height-paddingTop && ny > paddingBottom) {
                    break;
                }
            }
            y = ny;
        }
        sign *= -1;
    }
}

The strategy in this algorithm is the following:

  • Increment x-axis in the range of 160 + a random number between 0-40
  • Increment y-axis in the range of 60 + a random number between 0-40
  • Except: reverse the y-axis offset every other time.
  • Don't let the y value get too close to the top or bottom (paddingTop, paddingBottom)
  • Start offscreen to the left, and hardcode the second point to (0, winSize.height/2), so there's a hill coming up from the left offscreen.

Compile and run, and now you'll see a much better hill algorithm, that looks like maybe a properly motivated seal might be able to fly off these!

Better hill keypoints
[HillsBetter.jpg]

Drawing Part at a Time

Before you go much further, you need to make a major performance optimization. Right now, you're drawing all 1000 key points of the hills, even though only a few of them are visible on the screen at once!

So you could save a lot of time by simply calculating which key points to display based on the screen area, and display just those, as you can see below:

Optimization to draw visible points only

Let's try this out. Start by adding two instance variables to Terrain.m:

int _fromKeyPointI;
int _toKeyPointI;

Then add a new method called resetHillVertices above the init method

- (void)resetHillVertices {
    
    CGSize winSize = [CCDirector sharedDirector].winSize;
    
    static int prevFromKeyPointI = -1;
    static int prevToKeyPointI = -1;
    
    // key points interval for drawing
    while (_hillKeyPoints[_fromKeyPointI+1].x < _offsetX-winSize.width/8/self.scale) {
        _fromKeyPointI++;
    }
    while (_hillKeyPoints[_toKeyPointI].x < _offsetX+winSize.width*12/8/self.scale) {
        _toKeyPointI++;
    }

}

Here you loop through each of the key points (starting with 0) and look at their x-coordinates.

Whatever the current offset is set to, maps to the left edge of the screen. So you take that and subtract the winSize.width/8. If the value is less than that, you keep advancing till you find one that's greater. That's your from keypoint, a similar process is followed for the toKeypoint.

Now let's see if this works! Modify your draw method to the following:

- (void) draw {
    
    for(int i = MAX(_fromKeyPointI, 1); i <= _toKeyPointI; ++i) {
        ccDrawColor4F(1.0, 0, 0, 1.0); 
        ccDrawLine(_hillKeyPoints[i-1], _hillKeyPoints[i]);        
    }
    
}

Now instead of drawing all of the points, you only draw the visible ones by using the indices you calculated earlier. You also change the color of the line to red to make it a bit easier to see.

Next make a few more mods to Terrain.m to call resetHillVertices:

// Add at bottom of init
[self resetHillVertices];

// Add at bottom of setOffsetX
[self resetHillVertices];

One more thing - to make this easy to see, go to the bottom of your onEnter method in HelloWorldLayer.mm and add the following:

self.scale = 0.25;

Compile and run your code, and you should see the line segments pop in when it is time for them to be drawn!

Screenshot of game now drawing visible points only

Making Smooth Slopes

So far so good, but there is one big problem - those don't look like hills at all! In real life, hills don't go up and down in straight lines - they have slopes.

But how can you make your hills curved? Well one way to do it is with our friend cosine that we learned about back in high school!

Note: This tutorial covers the basics of trigonometry that you need to know to make this game, but if you'd like to learn more, check out our Trigonometry for Game Programming series!

As a quick refresher, here's what a cosine curve looks like:

Diagram of a cosine curve

So it starts at 1, and every PI it curves down to -1.

But how can you make use of this function to create a nice curve connecting the keypoints? Let's think about just two of them, as shown in the diagram below:
Cosine function mapped to hill

First, you need to draw the line in segments, so you'll create one segment every 10 points. Similarly, you want a complete cosine curve, so you can divide PI by the number of segments to get the delta angle at each point.

Then, you'll want to map cos(0) to the y-coordinate of p0, and cos(PI) to the y-coordinate of p1. To do this, you'll call cos(angle), and multiply the result by half the distance between p1 and p0 (called ampl in the diagram).

Since cos(0) = 1 and cos(PI) = -1, this gives us ampl at p0 and -ampl at p1. You can add that to the position of the midpoint to give you the y-coordinate you need!

Let's see what this looks like in code. First add the definition of the segment length to the top of Terrain.m:

#define kHillSegmentWidth 10

Then add the following to your draw method, right after the call to ccDrawLine:

ccDrawColor4F(1.0, 1.0, 1.0, 1.0);

CGPoint p0 = _hillKeyPoints[i-1];
CGPoint p1 = _hillKeyPoints[i];
int hSegments = floorf((p1.x-p0.x)/kHillSegmentWidth);
float dx = (p1.x - p0.x) / hSegments;
float da = M_PI / hSegments;
float ymid = (p0.y + p1.y) / 2;
float ampl = (p0.y - p1.y) / 2;

CGPoint pt0, pt1;
pt0 = p0;
for (int j = 0; j < hSegments+1; ++j) {
    
    pt1.x = p0.x + j*dx;
    pt1.y = ymid + ampl * cosf(da*j);
            
    ccDrawLine(pt0, pt1);
    
    pt0 = pt1;
    
}

This runs through the strategy we outlined in the diagram above. Take a minute and think through the code and make sure you understand how this works, because you'll be building on this from here.

One last thing - you don't need to zoom out anymore, so back in HelloWorldLayer.mm replace the scale in init back to 1.0:

self.scale = 1.0;

Compile and run, and now you should see a curvy line connecting the hills!

Debug drawing of hill slopes with cosine

Ali Hafizji

Contributors

Ali Hafizji

Author

Over 300 content creators. Join our team.