Chapter 10: Ready…

Now that we no longer have to worry about displaying text in our game, we need to start thinking about the targets our users will be touching for points (or missing for penalties).

The object of TouchTargets will be to hit targets on the iPhone screen. The player will start with five targets that will last for a few seconds each before they start to fade and then disappear. If the player hits a target, they will score 500 points and the target will disappear. A new target will pop up in its place somewhere else on the screen, and have a slightly shorter lifespan. If a target fades out and disappears without being touched, it is gone for the rest of that game. If the user touches somewhere on the screen where there is no target, they lose 100 points.

Once there are no targets left, that is, all targets have faded and disappeared without being touched, the game is over. The player’s score will be based on speed and accuracy, and the games will be pretty short. TouchTargets will be a fun diversion to pass around to your friends to see who has the fastest and most accurate reflexes.

So what are our challenges for the targets? We need to draw textured objects in OpenGL space, and we need to be able to determine if a touch location is in the target or not. We also need the targets to have a lifespan, since we want them to fade and disappear after some amount of time.

These targets are starting to sound like the text string objects, aren’t they? If you started thinking about creating a base class and extending it with a text string class and a target class, pat yourself on the back, you’re thinking like a true object-oriented developer.

For the purposes of this tutorial, however, we’re not going to do that, but it would make a great learning exercise if you wanted to try that later. Keeping all of the code in separate classes may increase the codebase somewhat, but I feel that it makes the tutorial easier to follow.

Speaking of being easier to follow, we’ve made quite a few changes to the Xcode OpenGL ES application template we started from. Since all of our new code is OpenGL ES 2.0 only, we should take a moment to clean up our view controller and flag our project as requiring OpenGL ES 2.0 to run.

First, let’s clean up the view controller. We’ll start by looking at the changes to the view controller’s header file, TouchTargetsViewController.h.

#import <UIKit/UIKit.h>

#import <OpenGLES/EAGL.h>

//#import <OpenGLES/ES1/gl.h> // ED: Removed
//#import <OpenGLES/ES1/glext.h> // ED: Removed
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>

//#import "EDOpenGLProgram.h" // ED: Removed
#import "EDTextStringManager.h"

@interface TouchTargetsViewController : UIViewController {
@private
    EAGLContext *context;
   
    BOOL animating;
    NSInteger animationFrameInterval;
    CADisplayLink *displayLink;
   
    //EDOpenGLProgram *myOpenGLProgram; // ED: Removed
    EDTextStringManager *myTextStringManager;
}

@property (readonly, nonatomic, getter=isAnimating) BOOL animating;
@property (nonatomic) NSInteger animationFrameInterval;

- (void)startAnimation;
- (void)stopAnimation;

@end

We no longer need the OpenGL ES 1.x header files, so we’ll get rid of those. We also turned off the bouncing rainbow square a while back, so let’s get rid of all of the sample code that was handling that functionality. Since we no longer have anything to render from the view controller (the rainbow square is all we were drawing), we can remove any references to the EDOpenGLProgram class. Don’t worry, we still need it for the text string rendering classes, and we’ll be using it again in the target and background classes.

With the header file cleaned up, we can move on to the class source code in TouchTargetsViewController.m.

#import <QuartzCore/QuartzCore.h>

#import "TouchTargetsViewController.h"
#import "EAGLView.h"

/* ED: Removed
// Uniform index.
enum {
    UNIFORM_TRANSLATE,
    NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];

// Attribute index.
enum {
    ATTRIB_VERTEX,
    ATTRIB_COLOR,
    NUM_ATTRIBUTES
};
GLint attributes[NUM_ATTRIBUTES];
 */


@interface TouchTargetsViewController ()
@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) CADisplayLink *displayLink;
@end

@implementation TouchTargetsViewController

@synthesize animating, context, displayLink;

Since we’ll no longer be drawing that rainbow square, let’s get rid of all the code that supported it. We’ll start by deleting the attribute and uniform arrays at the top of the program.

Next, the awakeFromNib method.

- (void)awakeFromNib
{
    EAGLContext *aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   
    //if (!aContext) { // ED: Removed
    //    aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; // ED: Removed
    //} // ED: Removed
   
    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];
   
    //if([context API] == kEAGLRenderingAPIOpenGLES2) { // ED: Removed
   
    /* ED: Removed
        myOpenGLProgram = [[EDOpenGLProgram alloc] init];
       
        [myOpenGLProgram setVertexShader:@"Shader"];
        [myOpenGLProgram setFragmentShader:@"Shader"];
       
        [myOpenGLProgram addAttributeLocation:@"ATTRIB_VERTEX" forAttribute:@"position"];
        [myOpenGLProgram addAttributeLocation:@"ATTRIB_COLOR" forAttribute:@"color"];
       
        [myOpenGLProgram addUniformLocation:@"UNIFORM_TRANSLATE" forUniform:@"translate"];
       
        [myOpenGLProgram compileAndLink];
       
        attributes[ATTRIB_VERTEX] = [myOpenGLProgram getAttributeIDForIndex:@"ATTRIB_VERTEX"];
        attributes[ATTRIB_COLOR] = [myOpenGLProgram getAttributeIDForIndex:@"ATTRIB_COLOR"];
       
        uniforms[UNIFORM_TRANSLATE] = [myOpenGLProgram getUniformIDForIndex:@"UNIFORM_TRANSLATE"];
     */

       
        myTextStringManager = [[EDTextStringManager alloc] initWithCharacterSheetName:@"FontCooperStd.png"];
       
        EDTextString *myTextString = [[EDTextString alloc] initWithString:@"Hello, World!"];
        [myTextString setPositionX:160 andY:240 andZ:0];
        [myTextString setColorRed:0 andGreen:0 andBlue:128];
        [myTextString setSize:20];
       
        [myTextStringManager addTextString:myTextString];

    //} // ED: Removed
   
    animating = FALSE;
    animationFrameInterval = 1;
    self.displayLink = nil;
}

Since we’re going full OpenGL ES 2.0, we no longer need to check if we’re 1.x or 2.0. All of the conditional logic that handled that can be removed, as well as the code that created a 1.x context if the 2.0 context failed to initialize properly.

Also, since we’re no longer drawing the rainbow square, we can remove all of that EDOpenGLProgram logic we put in earlier.

One neat thing to mention here is that since we pulled the OpenGL ES 2.0 program and shader code out and put it into it’s own class, adding or removing that OpenGL program and shader functionality has become very simple. This is one of the benefits of organizing your code well.

The only other method that has any changes, and the only other method we’ll look at, is the drawFrame method.

- (void)drawFrame
{
    [(EAGLView *)self.view setFramebuffer];
   
    // Replace the implementation of this method to do your own custom drawing.
    /* ED: Removed
    static const GLfloat squareVertices[] = {
        -0.5f, -0.33f,
        0.5f, -0.33f,
        -0.5f,  0.33f,
        0.5f,  0.33f,
    };
   
    static const GLubyte squareColors[] = {
        255, 255,   0, 255,
        0,   255, 255, 255,
        0,     0,   0,   0,
        255,   0, 255, 255,
    };
   
    static float transY = 0.0f;
     */

   
    glClearColor(0.98, 0.98, 0.98, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
   
    /* ED: Removed
    if ([context API] == kEAGLRenderingAPIOpenGLES2) {
        // Use shader program.
        glUseProgram([myOpenGLProgram programId]);
       
        // Update uniform value.
        glUniform1f(uniforms[UNIFORM_TRANSLATE], (GLfloat)transY);
        transY += 0.075f;
       
        // Update attribute values.
        glVertexAttribPointer(attributes[ATTRIB_VERTEX], 2, GL_FLOAT, 0, 0, squareVertices);
        glEnableVertexAttribArray(attributes[ATTRIB_VERTEX]);
        glVertexAttribPointer(attributes[ATTRIB_COLOR], 4, GL_UNSIGNED_BYTE, 1, 0, squareColors);
        glEnableVertexAttribArray(attributes[ATTRIB_COLOR]);
       
        // Validate program before drawing. This is a good check, but only really necessary in a debug build.
        // DEBUG macro must be defined in your debug configurations if that's not already the case.
#if defined(DEBUG)
        if (![myOpenGLProgram validate]) {
            NSLog(@"Failed to validate program: %d", [myOpenGLProgram programId]);
            return;
        }
#endif
    } else {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0.0f, (GLfloat)(sinf(transY)/2.0f), 0.0f);
        transY += 0.075f;
       
        glVertexPointer(2, GL_FLOAT, 0, squareVertices);
        glEnableClientState(GL_VERTEX_ARRAY);
        glColorPointer(4, GL_UNSIGNED_BYTE, 0, squareColors);
        glEnableClientState(GL_COLOR_ARRAY);
    }
     */

   
    //if([context API] == kEAGLRenderingAPIOpenGLES2) { // ED: Removed
        [myTextStringManager drawAllTextStrings];
    //} // ED: Removed
   
    [(EAGLView *)self.view presentFramebuffer];
}

I love hacking and slashing unneeded code, and there was a ton of it now in this method. Pretty much all of the code in the drawFrame method was for rendering that old rainbow square, so we can cut a great deal of code from here.

One very important thing to remember, though, is that the view controller is still responsible for our display link loop, and must still create a valid OpenGL ES 2.0 context so all of the other classes we’ll be creating and using will continue to function properly.

With much of the rendering processing moving into other classes, we can organize our code so that the view controller can focus on controlling, and the manager classes that the view controller controls can focus on making the worker classes do the work.

The last, very important thing we need to do is add a line to our .plist file to indicate that we require OpenGL ES 2.0. Open your .plist file in Xcode and add the following:

 


The “Required device capabilities” flag indicates that the target device for this app must have one or more specific capabilities in order to run this code properly. The “opengles-2” flag specifies that the required device capability is the ability to run openGL ES 2.0.

Finally, you can delete the old shaders, Shader.vsh and Shader.fsh, from the project. We’re not using them anymore.

Chapter 9 | Index | Chapter 11