How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X

Learn how to create dynamic textures similar to the hills in Tiny Wings using CCRenderTexture in this Cocos2D 2.X Tutorial. By Ali Hafizji.

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

Creating a Texture with Stripes

Before you start writing code to draw stripes, let’s take a minute to think about our strategy.

You will start by coloring the texture one color (say blue), and then you’ll draw several stripes diagonally across it (say green), as you can see in the diagram below:

Algorithm for drawing stripes

Notice that since the stripes are diagonal, you’re actually going to have to start the drawing outside of the bounds of the texture, and continue drawing some of the stripes outside of the bounds of the texture.

Also note that to get a nice 45 degree angle, you’ll offset V1 from V0 by the height of the texture (that way both the DX and the DY are textureHeight, hence a 45 degree angle).

OK, let’s see the code for this. Add a new method above the init method as follows:

-(CCSprite *)stripedSpriteWithColor1:(ccColor4F)c1 color2:(ccColor4F)c2 textureWidth:(float)textureWidth
   textureHeight:(float)textureHeight stripes:(int)nStripes {

    // 1: Create new CCRenderTexture
    CCRenderTexture *rt = [CCRenderTexture renderTextureWithWidth:textureWidth height:textureHeight];
    
    // 2: Call CCRenderTexture:begin
    [rt beginWithClear:c1.r g:c1.g b:c1.b a:c1.a];
    
    // 3: Draw into the texture
    
    // Layer 1: Stripes
    CGPoint vertices[nStripes*6];
    ccColor4F colors[nStripes*6];
    
    int nVertices = 0;
    float x1 = -textureHeight;
    float x2;
    float y1 = textureHeight;
    float y2 = 0;
    float dx = textureWidth / nStripes * 2;
    float stripeWidth = dx/2;
    for (int i=0; i<nStripes; i++) {
        x2 = x1 + textureHeight;
        
        vertices[nVertices] = CGPointMake(x1, y1);
        colors[nVertices++] = (ccColor4F){c2.r, c2.g, c2.b, c2.a};
        
        vertices[nVertices] = CGPointMake(x1+stripeWidth, y1);
        colors[nVertices++] = (ccColor4F){c2.r, c2.g, c2.b, c2.a};
        
        vertices[nVertices] = CGPointMake(x2, y2);
        colors[nVertices++] = (ccColor4F){c2.r, c2.g, c2.b, c2.a};
        
        vertices[nVertices] = vertices[nVertices-2];
        colors[nVertices++] = (ccColor4F){c2.r, c2.g, c2.b, c2.a};
        
        vertices[nVertices] = vertices[nVertices-2];
        colors[nVertices++] = (ccColor4F){c2.r, c2.g, c2.b, c2.a};
        
        vertices[nVertices] = CGPointMake(x2+stripeWidth, y2);
        colors[nVertices++] = (ccColor4F){c2.r, c2.g, c2.b, c2.a};
        x1 += dx;
    }
    
    self.shaderProgram =
    [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionColor];
    
    // Layer 2: Noise
    CCSprite *noise = [CCSprite spriteWithFile:@"Noise.png"];
    [noise setBlendFunc:(ccBlendFunc){GL_DST_COLOR, GL_ZERO}];
    noise.position = ccp(textureWidth/2, textureHeight/2);
    [noise visit];
    
    // Layer 3: Stripes
    CC_NODE_DRAW_SETUP();
    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_TRUE, 0, colors);
    glDrawArrays(GL_TRIANGLES, 0, (GLsizei)nVertices);

    // 4: Call CCRenderTexture:end
    [rt end];
    
    // 5: Create a new Sprite from the texture
    return [CCSprite spriteWithTexture:rt.sprite.texture];
}

Most of this method is review for how to create a CCRenderTexture, however the code to create the stripes layer is new so let's discuss that.

It first creates an array of vertices and colors - for each stripe, you need 6 vertices - 3 vertices times 2 triangles. You can't use triangle strip here, because the stripes aren't adjacent. The colors array stores the color at each vertex of the triangle.

The first vertex is at (-textureHeight, textureHeight) as you can see in the diagram above, and the next vertex is (-textureHeight+stripWidth, textureHeight). The third is at (0, 0) and the fourth is at (stripeWidth,0). These are the vertices you need for one stripe, and each time you advance by double the length of a stripe and continue until all stripes are done.

Now you can try this out by modifying your genBackground method as follows:

- (void)genBackground {
    
    [_background removeFromParentAndCleanup:YES];
    
    ccColor4F bgColor = [self randomBrightColor];
    ccColor4F color2 = [self randomBrightColor];
    //_background = [self spriteWithColor:bgColor textureSize:512];
    int nStripes = ((arc4random() % 4) + 1) * 2;
    _background = [self stripedSpriteWithColor1:bgColor color2:color2 
                        textureWidth:IS_IPHONE_5?1024:512 textureHeight:512 stripes:nStripes];
    
    self.scale = 0.5;
    
    CGSize winSize = [CCDirector sharedDirector].winSize;
    _background.position = ccp(winSize.width/2, winSize.height/2);
    [self addChild:_background];
    
}

This calls the new method, and also sets the scale of the layer to 0.5 to make it easier to see the entire texture.

Compile and run, and as you tap you should see randomly generated stripe textures!

Dynamically generated stripe

Repeating Backgrounds

For both the striped background and the gradient background, you want to be able to tile them across an area of space that may be wider than the texture.

A simple way of doing this would be to make multiple CCSprites and chain them together. But that would be crazy, because there's a simpler way - you can set up the textures so they repeat!

Try this out for yourself by making the following changes to HelloWorldLayer.mm:

// Add to genBackground, right BEFORE the call to addChild
ccTexParams tp = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
[_background.texture setTexParameters:&tp];

// Add to bottom of onEnter
[self scheduleUpdate];

// Add after onEnter
- (void)update:(ccTime)dt {
    
    float PIXELS_PER_SECOND = 100;
    static float offset = 0;
    offset += PIXELS_PER_SECOND * dt;
    
    CGSize textureSize = _background.textureRect.size;
    [_background setTextureRect:CGRectMake(offset, 0, textureSize.width, textureSize.height)];
    
}

The important part is the texture parameters:

  • GL_LINEAR is a fancy way of saying "when displaying the texture at a smaller or larger scale than the original size, take a weighted average of the nearby pixels."
  • GL_REPEAT is a fancy way of saying "if you try to index a texture at a coordinate outside the texture bounds, put what would be there if the texture were to continuously tile."

Also, it schedules an update that updates the visible part of the texture to be continuously moving forward along the x-axis. This allows you to see the texture repeating over time.

Compile and run and now you'll see a continuously scrolling and repeating texture! You can try this with the background gradient texture and that will work also.

Gratuitous Highlights

If you look at the Tiny Wings implementation of the hill texture, you'll see it has a slight highlight as top and a gradient along the bottom to make it look nicer. So let's modify our stripe texture for this.

Add the following right after the glDrawArrays call in stripedSpriteWithColor1:color2:textureWidth:textureHeight:stripes: method:

float gradientAlpha = 0.7;

nVertices = 0;

vertices[nVertices] = CGPointMake(0, 0);
colors[nVertices++] = (ccColor4F){0, 0, 0, 0};

vertices[nVertices] = CGPointMake(textureWidth, 0);
colors[nVertices++] = (ccColor4F){0, 0, 0, 0};

vertices[nVertices] = CGPointMake(0, textureHeight);
colors[nVertices++] = (ccColor4F){0, 0, 0, gradientAlpha};

vertices[nVertices] = CGPointMake(textureWidth, textureHeight);
colors[nVertices++] = (ccColor4F){0, 0, 0, gradientAlpha};

glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_TRUE, 0, colors);
glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)nVertices);

// layer 3: top highlight
float borderHeight = textureHeight/16;
float borderAlpha = 0.3f;
nVertices = 0;

vertices[nVertices] = CGPointMake(0, 0);
colors[nVertices++] = (ccColor4F){1, 1, 1, borderAlpha};

vertices[nVertices] = CGPointMake(textureWidth, 0);
colors[nVertices++] = (ccColor4F){1, 1, 1, borderAlpha};

vertices[nVertices] = CGPointMake(0, borderHeight);
colors[nVertices++] = (ccColor4F){0, 0, 0, 0};

vertices[nVertices] = CGPointMake(textureWidth, borderHeight);
colors[nVertices++] = (ccColor4F){0, 0, 0, 0};

glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_TRUE, 0, colors);
glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)nVertices);

This code should be review by now, but the first part creates a gradient like you did for the gradient background earlier, and the second part adds a highlight to the top part of the stripes to make it look like the sun is shining on it a bit.

Compile and run, and now you should see your stripes looking even better, with a gradient and a highlight!

Finished dynamic background with stripes, highlight, gradient, and shading mask

Ali Hafizji

Contributors

Ali Hafizji

Author

Over 300 content creators. Join our team.