How To Create A Simple 2D iPhone Game with OpenGL ES 2.0 and GLKit – Part 2

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer. In this tutorial series, we are creating a very simple game for the iPhone using the lowest level APIs on iOS – OpenGL ES 2.0 and GLKit. In the first part of the series, we created a basic OpenGL […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

In this tutorial series, we are creating a very simple game for the iPhone using the lowest level APIs on iOS – OpenGL ES 2.0 and GLKit.

In the first part of the series, we created a basic OpenGL 2.0 and GLKit project, created a class for Sprites, rendered them to the scene, and made them move.

In this second and final part of the series, we’ll let the ninja shoot stars, kill monsters, and win the game!

Keep reading to become a code ninja and finish this game!

Shooting Projectiles

We’re going to implement shooting by registering a tap gesture recognizer on the view. When we receive a tap, we’ll perform the following steps:

  1. Convert the touch to OpenGL coordinates.
  2. Figure out the direction (vector) of the touch, with respect to the ninja.
  3. Normalize the vector so that it is a length of 1. This makes it easy to make a vector pointing in the same direction as the offset vector, but with a particular length, which we’ll need in the next step.
  4. Multiply the normalized offset by the velocity we want the ninja star to move. We now have a vector pointing in the same direction as the diference between the touch and the ninja, but a length based on where we want the ninja star to be after 1 second.
  5. Create a ninja star sprite and set its velocity based on the above.

Here’s a diagram of the above:

Calculating the move velocity

Now let’s see this in code. Make the following changes to SGGViewController.m:

// Add to bottom of viewDidLoad
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapFrom:)];                                                               
    [self.view addGestureRecognizer:tapRecognizer];

// Add new method to file (anywhere)
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer { 
        
    // 1
    CGPoint touchLocation = [recognizer locationInView:recognizer.view];
    touchLocation = CGPointMake(touchLocation.x, 320 - touchLocation.y);
    
    // 2
    GLKVector2 target = GLKVector2Make(touchLocation.x, touchLocation.y);
    GLKVector2 offset = GLKVector2Subtract(target, self.player.position);
    
    // 3
    GLKVector2 normalizedOffset = GLKVector2Normalize(offset);
    
    // 4
    static float POINTS_PER_SECOND = 480;  
    GLKVector2 moveVelocity = GLKVector2MultiplyScalar(normalizedOffset, POINTS_PER_SECOND);
        
    // 5
    SGGSprite * sprite = [[SGGSprite alloc] initWithFile:@"Projectile.png" effect:self.effect];
    sprite.position = self.player.position;
    sprite.moveVelocity = moveVelocity;
    [self.children addObject:sprite];
        
}

The numbered steps here correspond to the same steps described above – flip back if you’re confused.

Compile and run, and now you should be able to tap to shoot ninja stars!

Shooting ninja stars

Collision Detection

So now we have shurikens flying everywhere – but what our ninja really wants to do is to lay some smack down. So let’s add in some code to detect when our projectiles intersect our targets.

First, we need to add a new method to SGGSprite in order to return the bounding box for the sprite on the screen. We need this so we can check if two sprites collide.

Add the following to SGGSprite.h:

- (CGRect)boundingBox;

And then add the implementation to SGGSprite.m:

- (CGRect)boundingBox {
    CGRect rect = CGRectMake(self.position.x, self.position.y, self.contentSize.width, self.contentSize.height);
    return rect;
}

The next thing we have to do is keep better track of the targets and projectiles currently in the scene. Make the following changes to SGGViewController.m:

// Add to private @interface
@property (strong) NSMutableArray *projectiles;
@property (strong) NSMutableArray *targets;
@property (assign) int targetsDestroyed;

// Add to synthesize section
@synthesize projectiles = _projectiles;
@synthesize targets = _targets;
@synthesize targetsDestroyed = _targetsDestroyed;

// Add to bottom of viewDidLoad
self.projectiles = [NSMutableArray array];
self.targets = [NSMutableArray array];

// Add to bottom of handleTapFrom
[self.projectiles addObject:sprite];

// Add to bottom of addTarget
[self.targets addObject:target];

Now we have an array of the targets and projectiles currently in the scene. We also added a variable to keep track of the number of targets destroyed (we’ll need this later).

Finally, add the meat of the logic to the beginning of the update method:

NSMutableArray * projectilesToDelete = [NSMutableArray array];
for (SGGSprite * projectile in self.projectiles) {
    
    NSMutableArray * targetsToDelete = [NSMutableArray array];
    for (SGGSprite * target in self.targets) {            
        if (CGRectIntersectsRect(projectile.boundingBox, target.boundingBox)) {
            [targetsToDelete addObject:target];
        }            
    }
    
    for (SGGSprite * target in targetsToDelete) {
        [self.targets removeObject:target];
        [self.children removeObject:target];
        _targetsDestroyed++;
    }
    
    if (targetsToDelete.count > 0) {
        [projectilesToDelete addObject:projectile];
    }
}

for (SGGSprite * projectile in projectilesToDelete) {
    [self.projectiles removeObject:projectile];
    [self.children removeObject:projectile];
}

The above should be pretty clear. We just iterate through our projectiles and targets, creating rectangles corresponding to their bounding boxes, and use CGRectIntersectsRect to check for intersections. If any are found, we remove them from the scene and from the arrays. Note that we have to add the objects to a “toDelete” array because you can’t remove an object from an array while you are iterating through it. Again, there are more optimal ways to implement this kind of thing, but I am going for the simple approach.

Compile and run, and now when your projectiles intersect targets they should disappear!

Gratuitous Music and Sound Effects

If you’ve been following the tutorials on this site, you’ll know that we can never leave you hanging without some awesome music and sound effects for our game tutorials :]

You should already have some cool background music I made (background-music-aac.caf) and my awesome pew-pew sound effect (pew-pew.wav) in your project, from the resources for this tutorial that you added to the project in part 1.

We’re going to cheat a little bit in this part of the tutorial and use a premade sound engine, CocosDenshion. CocosDenshion is actually the sound engine that comes with Cocos2D, but you can use it in any project just by including the files.

We could play the music and effects ourselves by using the easy-to-use AVAudioPlayer, but you’ll find that performance is not up-to-snuff for playing rapid sound effects like we want for this game. Alternatively you could use OpenAL yourself, but the focus of this tutorial is not on OpenAL so we are going to go the easy route.

So go ahead and add the CocosDenshion files to your project. You can copy them from your Cocos2D installation, any Cocos2D project, or just be lazy and download a CocosDenshion zip I made.

If you try to compile your project you’ll get a ton of errors, because CocosDenshion is not ARC compatible. Luckily fixing this is easy. Select your project in the Project Navigator, select the SimpleGLKitGame target, and go to the Build Phases tab.

Double click CDAudioManager.m, and a popup will appear. Enter -fno-objc-arc into the box – this makes them compile without ARC support.

Turning off ARC support for CocosDenshion

Sadly you can only do one file at a time, so repeat this process for all 4 of the CocosDenshion files:

  • CDAudioManager.m
  • CDOpenALSupport.m
  • CocosDenshion.m
  • SimpleAudioEngine.m

CocosDension also requires some libraries. Sitll in the Build Phases tab, expand the Link Binary With Libraries section and add the following libraries:

  • AudioToolbox.framework
  • OpenAL.framework
  • AVFoundation.framework

Adding required frameworks for CocosDenshion

Your project should build OK now. Phew – hard part is done – now let’s play some music! Make the following changes to SGGViewController.m:

// Add to top of file
#import "SimpleAudioEngine.h"

// Add to end of viewDidLoad
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background-music-aac.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"pew-pew.wav"];

// Add to end of handleTapFrom
[[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew.wav"];

Compile and run, and enjoy the groovy tunes!

Contributors

Over 300 content creators. Join our team.