Chapter 14: A Little Background

In the last chapter, we put the finishing touches on our scoring system and added a much needed delay between finishing a game and starting a new one. As groovy as our application has become, it still looks pretty bland.

The last thing we’re going to do with this application is add a background, and we’re going to animate it.

Our new EDScrollingBackground class will create a backdrop and slowly scroll it sideways while we play our game. It will add some vibrancy and life to our game, and be very easy to add.

Since this will be such a simple class, and since there will only be one background, we don’t need a manager class. The EDScrollingBackground class will do it all. Let’s look at the new EDScrollingBackground.h header file.

#import <Foundation/Foundation.h>

#import "EDOpenGLProgram.h"
#import "EDOpenGLTools.h"

@interface EDScrollingBackground : NSObject {
    EDOpenGLProgram *myProgram;
    GLuint myTexture;
   
    BOOL scrolling;
}

- (EDScrollingBackground *)init;
- (void)startScrolling;
- (void)stopScrolling;
- (void)draw;

@end

Since we have no manager class to handle the OpenGL drawing for us, we’ll do it ourself. We’ll need an EDOpenGLProgram instance variable, and a texture. We’ll also keep track of whether we’re scrolling or not with the scrolling flag.

Let’s look at the class source file, EDScrollingBackground.m.

#import "EDScrollingBackground.h"

#define SCROLL_AMOUNT -0.0005

enum {
    ATTRIBUTE_VERTEX,
    ATTRIBUTE_TEXTURE_COORD,
    NUM_ATTRIBUTES
};
GLint myAttributes[NUM_ATTRIBUTES];

enum {
    UNIFORM_TEXTURE,
    NUM_UNIFORMS
};
GLint myUniforms[NUM_UNIFORMS];

static GLfloat myVertexArray[] = {
    -1, -1,  0,
     1, -1,  0,
    -1,  1,  0,
     1,  1,  0,
    -1,  1,  0,
     1, -1,  0
};

GLfloat myTextureCoordArray[12];

@implementation EDScrollingBackground

The first thing we see after the #import statement is a #define to control the scroll speed. This speed is in OpenGL space, which runs from -1 to 1.

Since we’ll be drawing ourself, we also have the familiar attribute and uniform arrays to increase performance, and we’re going to hardcode our vertex array, since it will never change. The triangles we’re going to draw to display the background texture will extend for the full width and height of OpenGL space, from (-1,-1) through (1,1).

The texture array can’t be hardcoded, though, because we’ll be adjusting the coordinates slightly at each render to achieve the scrolling effect.

- (EDScrollingBackground *)init {
    if((self = [super init])) {
        [EDOpenGLTools loadTexture:&myTexture fromFile:@"background.png"];
       
        myProgram = [[EDOpenGLProgram alloc] init];
       
        [myProgram setVertexShader:@"EDScrollingBackground"];
        [myProgram setFragmentShader:@"EDScrollingBackground"];
       
        [myProgram addAttributeLocation:@"ATTRIBUTE_VERTEX" forAttribute:@"vertex"];
        [myProgram addAttributeLocation:@"ATTRIBUTE_TEXTURE_COORD" forAttribute:@"texture_coord"];

        [myProgram addUniformLocation:@"UNIFORM_TEXTURE" forUniform:@"texture"];
       
        [myProgram compileAndLink];
       
        myAttributes[ATTRIBUTE_VERTEX] = [myProgram getAttributeIDForIndex:@"ATTRIBUTE_VERTEX"];
        myAttributes[ATTRIBUTE_TEXTURE_COORD] = [myProgram getAttributeIDForIndex:@"ATTRIBUTE_TEXTURE_COORD"];
       
        myUniforms[UNIFORM_TEXTURE] = [myProgram getUniformIDForIndex:@"UNIFORM_TEXTURE"];
       
        // Load texture array - we will be modifying this to achieve the scrolling effect
       
        myTextureCoordArray[0] = 0;
        myTextureCoordArray[1] = 0;
       
        myTextureCoordArray[2] = 1;
        myTextureCoordArray[3] = 0;
       
        myTextureCoordArray[4] = 0;
        myTextureCoordArray[5] = 1;
       
        myTextureCoordArray[6] = 1;
        myTextureCoordArray[7] = 1;
       
        myTextureCoordArray[8] = 0;
        myTextureCoordArray[9] = 1;
       
        myTextureCoordArray[10] = 1;
        myTextureCoordArray[11] = 0;
       
        scrolling = FALSE;
    }
   
    return self;
}

We use the EDOpenGLTools loadTexture:fromFile: class message to load our background texture, then use the EDOpenGLProgram class to create, compile, and link the program we’ll be using to render the background.

After that, we load up the texture coordinate array with the default values for the background texture.

- (void)startScrolling {
    scrolling = TRUE;
}

- (void)stopScrolling {
    scrolling = FALSE;
}

The startScrolling and stopScrolling methods do nothing more than flip the scrolling flag TRUE or FALSE.

- (void)draw {
    glUseProgram([myProgram programId]);
   
    glBindTexture(GL_TEXTURE_2D, myTexture);
   
    glUniform1i(myUniforms[UNIFORM_TEXTURE], 0);
   
    glVertexAttribPointer(myAttributes[ATTRIBUTE_VERTEX], 3, GL_FLOAT, 0, 0, myVertexArray);
    glEnableVertexAttribArray(myAttributes[ATTRIBUTE_VERTEX]);
   
    glVertexAttribPointer(myAttributes[ATTRIBUTE_TEXTURE_COORD], 2, GL_FLOAT, 0, 0, myTextureCoordArray);
    glEnableVertexAttribArray(myAttributes[ATTRIBUTE_TEXTURE_COORD]);

#if defined(DEBUG)
    if(![myProgram validate]) {
        NSLog(@"Validate program [%d] failed!", [myProgram programId]);
        return;
    }
#endif
   
    glDrawArrays(GL_TRIANGLES, 0, 6);
   
    // If scrolling, update texture coordinates
   
    if(scrolling == TRUE) {
        myTextureCoordArray[0] += SCROLL_AMOUNT;
        myTextureCoordArray[2] += SCROLL_AMOUNT;
        myTextureCoordArray[4] += SCROLL_AMOUNT;
        myTextureCoordArray[6] += SCROLL_AMOUNT;
        myTextureCoordArray[8] += SCROLL_AMOUNT;
        myTextureCoordArray[10] += SCROLL_AMOUNT;
    }
}

@end

The draw method, designed to be called from our game control loop (the view controller’s drawFrame method) will execute the OpenGL calls necessary to render our background.

If the scrolling flag is set to TRUE, we will also apply our SCROLL_AMOUNT value to the texture coordinate S values. Here we’re taking advantage of the default behavior of OpenGL to tile textures, creating a smooth scrolling effect, assuming that our background tiles nicely along the X axis.

Speaking of backgrounds, don’t forget that the background dimensions should be power of 2, since we’ll be using this image as an OpenGL texture. If you’re worried about your background images being squished when it’s resized for the iPhone’s 320 x 480 screen, here’s a trick. Create your backgrounds at whatever resolution you’re targeting, like 320 x 480 for the iPhone. When you’re finished creating the background, resize the image to 512 x 512. The image will be stretched in width and height, but when you load it in and display it on the iPhone it will be resized back to 320 x 480 and look perfect.

Here’s our background image in it’s stretched form at 512 x 512.

 

 

And here’s what it looks like on the iPhone.

 

 

I know it’s hard to tell, but trust me, the iPhone version is the one that looks like my original background design.

Let’s make the changes to our view controller so we can see it first hand.

- (void)awakeFromNib
{
    EAGLContext *aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   
    if (!aContext)
        NSLog(@"Failed to create ES context");
    else if (![EAGLContext setCurrentContext:aContext])
        NSLog(@"Failed to set ES context current");
   
    self.context = aContext;
    [aContext release];
   
    [(EAGLView *)self.view setContext:context];
    [(EAGLView *)self.view setFramebuffer];
   
    myTextStringManager = [[EDTextStringManager alloc] initWithCharacterSheetName:@"FontCooperStd.png"];
   
    myStatusMessage = [[EDTextString alloc] initWithString:@"Touch to Start"];
    [myStatusMessage setPositionX:160 andY:240 andZ:0];
    [myStatusMessage setSize:20];
   
    [myTextStringManager addTextString:myStatusMessage];
   
    myScoreMessage = [[EDTextString alloc] initWithString:@"Score: 0"];
    [myScoreMessage setPositionX:160 andY:25 andZ:0];
    [myScoreMessage setSize:20];
   
    [myTextStringManager addTextString:myScoreMessage];
   
    myTargetManager = [EDTargetManager sharedInstance];
   
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &myScreenWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &myScreenHeight);

    animating = FALSE;
    animationFrameInterval = 1;
    self.displayLink = nil;
   
    gameOverCounter = -1;
   
    myScrollingBackground = [[EDScrollingBackground alloc] init]; // ED: Added
    [myScrollingBackground startScrolling]; // ED: Added  
}

The only changes we have to make are the two lines added to the bottom of the method. The first allocates and instantiates the EDScrollingBackground object, and the second starts the scrolling action.

The only other change is in the drawFrame method.

- (void)drawFrame
{
    [(EAGLView *)self.view setFramebuffer];
   
    glClearColor(0.98, 0.98, 0.98, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
   
    [myScrollingBackground draw]; // ED: Added
   
    [myTargetManager drawAllTargets];
   
    [myTextStringManager drawAllTextStrings];
   
    if(gameOver == FALSE && [myTargetManager numActiveTargets] == 0) {
        gameOver = TRUE;
       
        if([myStatusMessage isAlive] == FALSE) {
            gameOverCounter = 100;
           
            [myStatusMessage setString:@"Game Over!"];
            [myTextStringManager addTextString:myStatusMessage];
        }
    } else if(gameOver == TRUE) {
        if(gameOverCounter > 0) {
            gameOverCounter--;
        } else if(gameOverCounter == 0) {
            [myStatusMessage setString:@"Touch to Start"];
            gameOverCounter--;
        }
    }
   
    [(EAGLView *)self.view presentFramebuffer];
}

The only change to the drawFrame method is the addition of the call to the draw method in the myScrollingBackground object. Just remember to put the background draw call before any other draw calls and you’re golden.

That’s all there is to it, let’s compile and run the app to see how it looks.

 

 

We have dynamic floating and static text, targets we can hit (or miss), scoring, and a colorful scrolling background. Ladies and gentlemen, I think we have an app. Nothing we can retire on, but it’s still not too shabby at all.

But wait! Before you download the Xcode project source and go, it’s very important that you read the next chapter.

Chapter 13 | Index | Chapter 15