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 3 of 3 of this article. Click here to view the first page.

Drawing the Hill

Now that you know how to get the curve representing the top of the hill, it's fairly simple to write the code to draw the hill using the striped texture we generated in the last tutorial!

The plan is for each segment of the hill, you'll compute the two triangles needed to render the hill, as you can see in the diagram below:

Drawing hills with an OpenGL triangle strip

You'll also set the texture coordinate at each point. For the x-coordinate, you'll simply divide it by the texture's width (since the texture repeats). For the y-coordinate though, you'll map the bottom of the hill to 0 and the top of the hill to 1, distributing the full height of the texture along the strip.

To implement this, first make a few modifications to Terrain.m:

// Add some new defines up top
#define kMaxHillVertices 4000
#define kMaxBorderVertices 800 

// Add some new instance variables inside the @interface
int _nHillVertices;
CGPoint _hillVertices[kMaxHillVertices];
CGPoint _hillTexCoords[kMaxHillVertices];
int _nBorderVertices;
CGPoint _borderVertices[kMaxBorderVertices];

Then add the following code at the bottom of resetHillVertices in Terrain.m:

float minY = 0;
if (winSize.height > 480) {
    minY = (1136 - 1024)/4;
}
if (prevFromKeyPointI != _fromKeyPointI || prevToKeyPointI != _toKeyPointI) {
    
    // vertices for visible area
    _nHillVertices = 0;
    _nBorderVertices = 0;
    CGPoint p0, p1, pt0, pt1;
    p0 = _hillKeyPoints[_fromKeyPointI];
    for (int i=_fromKeyPointI+1; i<_toKeyPointI+1; i++) {
        p1 = _hillKeyPoints[i];
        
        // triangle strip between p0 and p1
        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;
        pt0 = p0;
        _borderVertices[_nBorderVertices++] = pt0;
        for (int j=1; j<hSegments+1; j++) {
            pt1.x = p0.x + j*dx;
            pt1.y = ymid + ampl * cosf(da*j);
            _borderVertices[_nBorderVertices++] = pt1;
                                           
            _hillVertices[_nHillVertices] = CGPointMake(pt0.x, 0 + minY);
            _hillTexCoords[_nHillVertices++] = CGPointMake(pt0.x/512, 1.0f);
            _hillVertices[_nHillVertices] = CGPointMake(pt1.x, 0 + minY);
            _hillTexCoords[_nHillVertices++] = CGPointMake(pt1.x/512, 1.0f);
            
            _hillVertices[_nHillVertices] = CGPointMake(pt0.x, pt0.y);
            _hillTexCoords[_nHillVertices++] = CGPointMake(pt0.x/512, 0);
            _hillVertices[_nHillVertices] = CGPointMake(pt1.x, pt1.y);
            _hillTexCoords[_nHillVertices++] = CGPointMake(pt1.x/512, 0);
            
            pt0 = pt1;
        }
        
        p0 = p1;
    }
    
    prevFromKeyPointI = _fromKeyPointI;
    prevToKeyPointI = _toKeyPointI;        
}

Much of this will look familiar, since I already covered it in the previous section when drawing the hill curve with cosine.

The new part is you're now filling up an array with the vertices for each segment of the hill, as explained in the strategy part above. Each strip requires four vertices and four texture coords.

With that in place, you can now add the following code to the top of your draw method to make use of it:

CC_NODE_DRAW_SETUP();
    
ccGLBindTexture2D(_stripes.texture.name);
ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position | kCCVertexAttribFlag_TexCoords);
    
ccDrawColor4F(1.0f, 1.0f, 1.0f, 1.0f);
glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, _hillVertices);
glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, _hillTexCoords);

glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)_nHillVertices);

Also add this one line before the call to generateHills in the init method:

self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture];

This binds the stripes texture as the texture to use, passes in the array of vertices and texture coordinates made earlier, and draws the arrays as a triangle strip.

Also, you should comment out the lines of code that draw the hill lines and curve, since you're about to see something awesome and don't want debug drawing to spoil it! :]

Compile and run your code, and you should now see some awesome looking hills!

Awesome looking hills

Imperfections?

If you look closely at the hills, you may notice some imperfections such as this:

Imperfections in hill texture mapping

Some people in the Cocos2D forums seem to indicate that you can get this problem to go away by adding more vertical segments (instead of the 1 segment we have now).

However, I've personally found that while adding vertical segments doesn't help the quality, increasing the horizontal segments does. Open up Terrain.m and modify kHillSegmentWidth like the following:

#define kHillSegmentWidth 5

By reducing the width of each segment, you force the code to generate more segments to fill the space.

Run again, and you'll see your hills look much better! Of course, the tradeoff is processing time.

Totally beautiful hills

Where To Go From Here?

Here is a sample project with all of the code from the above tutorial.

Next check out part 2 of this tutorial, where finally we'll add some gameplay code so you can make one lucky seal start to fly!

Ali Hafizji

Contributors

Ali Hafizji

Author

Over 300 content creators. Join our team.