7. Blowing Stuff Up

In Chapter 6, the enemy was introduced, but the player couldn’t shoot or be shot. In this chapter, you’ll load up some collision detection and explosion animation, along with the logic to destroy enemies.

Collision Detection Methods

So what is collision detection? It’s a check to see if two sprites occupy the same space to determine, for example, if a missile has hit a player or enemy sprite. A 2D game such as Raiders uses three main methods of collision detection.

Bounding Box Detection

Bounding box detection checks the bounds of a sprite to see if they cross the bounds of another sprite. A simple diagram paints the picture (Figure 7.1).

Figure 7.1 The left image has no collision, but the right image is a hit!

image

The red boxes represent the bounding boxes of the sprites. In the leftmost sprites, the missile isn’t within the player sprite’s bounding box, so no collision is detected. In the rightmost sprites, the missile is inside the player sprite’s bounding box, so a collision does occur.

The code to perform collision detection is simple. In fact, the code is already being used to detect taps on the ActionItem:

- (BOOL)hasCollidedBoundingBox:(CGRect)otherSprite {
    return CGRectIntersectsRect(self.bounds, otherSprite);
}

For the sake of completeness, the actual algorithm for bounding box detection is

isCollision = (x1 + width1 > x2 &&
                x1 < x2 + width2 &&
                y1 + height1 > y2 &&
                y1 < y2 + height2)

where x1, y1 are the origin points of Box 1, and width1 and height1 are the width and height of Box 1. Similar variables exist for Box 2. Incidentally, it doesn’t matter which missile or sprite you use for Box 1 or Box 2.

Bounding Circle Detection

Bounding circle detection is similar to bounding box detection, except that it uses circular or spherical bounds. To detect a bounding circle collision, each sprite is given a center point and a radius, which is usually the larger of the width or height value. To detect the collision, the distance between the two sprites’ center points is checked against the sum of the sprites’ radii.

In Figure 7.2 the distance between the two centers on the left is larger than the sum of the circles’ radii. On the right, the sum of the radii is smaller than the distance between the centers, which indicates a collision. The detection code is:

- (BOOL)hasCollidedBoundingCircle:(CGRect)otherSprite {
    //choose height as sprites are largely rectangular in shape
    float radius1 = self.height;
    float radius2 = otherSprite.size.height;
    CGPoint center1 = CGPointMake(self.width / 2 + self.bounds.origin.x, self.height / 2 + self.bounds.origin.y);
    CGPoint center2 = CGPointMake(otherSprite.size.width / 2 + otherSprite.origin.x , otherSprite.size.height + otherSprite.origin.y);
    float distX = center1.x - center2.x;
    float distY = center1.y - center2.y;
    float distance = sqrtf((distX * distX) + (distY * distY));
    return distance <= radius1 + radius2;
}

Figure 7.2 Left is not a bounding circle collision, but right is.

image

Per Pixel Detection

Per pixel detection is a throwback to the old days of game programming when sprites were 2D collections of pixels, rather than texture-mapped cubes. Pixel-based detection looks at the non-transparent pixels in a sprite to see if they collide with the pixels in another sprite.

At one time, pixel detection was implemented in the GPU (graphics processing unit), and as a result, was efficient and quite popular. It is rarely used today because calculating the pixels in a texture can be computationally expensive—not to mention tricky—as you have to write additional rendering code to “extract” the pixels of the texture and perform the collision calculation using the CPU.

Collision Detection in Raiders

The sprites in Raiders are generally rectangular in shape, therefore a bounding box detection method will be the most effective and accurate. This game doesn’t require the accuracy of per pixel detection.

Also, bounding circle detection is best when sprites fit neatly into a circle—for example, when your game features a ball, or round projectiles. (Bounding circle detection can be more CPU efficient too, so it’s worth keeping in mind for future projects.)

Adding Detection Code

In Chapter 6, you enabled the enemy sprite to shoot missiles. Now you’ll program some ammo for the player and allow him to shoot missiles, too. The shooting code for EnemySprite is the same as the code for PlayerSprite; and because EnemySprite inherits from PlayerSprite, that code already resides in PlayerSprite.

Each missile gets a tag to identify it as a player or enemy missile, as defined in Common.h.

#define ENEMY_MISSILE  1000
#define PLLAYER_MISSILE 2000

The AbstractSceneController class now has a new method that checks for collisions:

- (BOOL)checkForCollisions:(Sprite *)spriteToCheck;

PlayerSprite also gets a new property called hasBeenShot, which should be self-explanatory.

Collision Checks and Effects

You have a lot to think about when performing collision checking. The first and most obvious consideration is whether the collision represents “missile shoots player” or “missile shoots enemy.”

If the player is shot, the game state must reset. If an enemy is shot, the enemy and player missiles must be removed from the screen, and an explosion animation (and perhaps a sound effect) are played. If the shot enemy is on a strafing run, that run must be ended.

All of these checks happen in the game loop, which in Raiders is the playScene method.

Collision Logic

When a sprite is hit, instead of removing it from the list of sprites, the value of the property hasBeenShot is set to YES. When the sprite is hidden rather than removed, it is easier to code and understand. The code can remain the same and loop through the sprite list and do any strafing only if the sprite’s hadBeenShot is NO. If the sprite is removed from the list, the code would have to understand which sprite is currently active, which adds slightly more complexity. Having said that, if your game has hundreds of sprites, it is more memory efficient to remove rather than hide sprites, and possibly more performant, too.

The playScene method now checks to see if sprite.hasBeenShot is YES or NO. If it is YES, then the sprite is not drawn and the loop continues; if NO, then the sprite is drawn.

Likewise playScene now checks if the sprite that is equal to the itemStrafing variable has been shot. If YES, then the itemStrafing variable is incremented and continues out of the loop; if NO, then the sprite continues on the strafing run. The code for these checks is:

if (itemStrafing == sprite.tag) {
    if (sprite.hasBeenShot) {
        [self strafingFinished];
        continue;
    }
    if (!sprite.isStrafing)
        [sprite startRun:playerSprite.currentPosition];
    else
        sprite.playersCurrentPosition = playerSprite.currentPosition;
}
if (!sprite.hasBeenShot)
    [sprite drawPlayer];
else
    continue;

Note that [self strafingFinished] is a method that increments the itemStrafing variable and checks to see if itemStrafing must be reset back to the start.

The actual collision detection is a two-step process. If the enemy missile is active, you check for a hit to the player. If the playerMissile is active, you check for a hit against an enemy:

if (sprite.isMissileActive) {
    if ([self checkForCollisions:sprite.missile]) {
        sprite.isMissileActive = NO;
        [self resetScene];
    }
}
if (playerSprite.isMissileActive) {
    if ([self checkForCollisions:sprite]) {
        playerSprite.isMissileActive = NO;
        sprite.hasBeenShot = YES;
        if (sprite.tag == itemStrafing)
            [self strafingFinished];
    }
}

The actual collision code looks like:

- (BOOL)checkForCollisions:(Sprite *)spriteToCheck {
    if (spriteToCheck.tag == ENEMY_MISSILE) {
        if ([spriteToCheck hasCollidedBoundingBox:playerSprite.bounds])
            return YES;
    }
    else if (playerSprite.isMissileActive && [playerSprite.missilehasCollidedBoundingBox:spriteToCheck.bounds]) {
        Explode *explode = [[Explode alloc] initWithPoint:CGPointMake(spriteToCheck.bounds.origin.x, spriteToCheck.bounds.origin.y)];
        [self addExplosion:explode];
        return YES;
    }
    return NO;
}

As the same detection method is used for both player and enemy collisions, you also need to check which type of collision is detected. If it’s an enemy missile collision, check against the bounding box of the playerSprite. If it’s a player missile collision, check against the currently active enemy sprite.

If the playerSprite was hit, then YES is returned from checkForCollisions: and the game state is reset. If an enemySprite is hit, an explosion object is created and added to the list of explosions to draw.

Setting Off Explosions

Figure 7.3 An explosion in action

image

The code additions for Chapter 7 include a new class called Explode whose sole function is to display an explosion animation.

The class contains an array of sprite objects, which are the parts of the explosion animation; a CGPoint where the explosion should be placed (which is the location of the hit sprite); and a variable that tracks the currently drawn animation frame.

As more than one explosion can occur at one time, an array of explosions is defined in AbstractSceneController.

To put Explode into action, the explosionList is looped through and the current sprite in the animation is drawn. You have two ways to get the current sprite to draw: the method currentSprite (which returns the correct frame), and doAnimation, which is called from the level1SceneController. The loop has to be called twice, in both the AbstractSceneController and the Level1SceneController.

AbstractSceneController just uses currentSprite to set the current sprite, while Level1SceneController calls doAnimation, which sets the current sprite and also increments the frame value. If doAnimation were called in both places, the animation would skip a frame, because as you may remember, the currentScene—in this case Level1Scene—draws the scene in memory, and AbstractSceneController actually renders it to the screen. Therefore, the scene would be drawn twice, but visible only once.

When all the frames of the explosion have been drawn, the BOOL hasAnimationFinished is set to YES, the animation stops, and the explosion is removed from the list of explosions:

for (int i = 0; i < explosionList.count; i++) {
            Explode *explode = [explosionList objectAtIndex:i];
            if ([explode isKindOfClass:[Explode class]]) {
                if (!explode.hasAnimationFinished) {
                    Sprite *sprite = [explode currentSprite];
                    [sprite updateTransforms];
                }
                else {
                    [self removeExplosion:explode];
                }
            }
        }

Note that fast enumeration isn’t used in this instance to loop through the list because you can’t remove an object from a list that is being enumerated.

Adding Sound Effects

Along with explosion animations, sound effects make your game a bit more enjoyable. In iOS, you have a couple of ways to play sound, depending on your needs.

A framework called AVAudioPlayer is very powerful because it allows you to perform several functions with audio playback including:

• Playing loops

• Playing from a particular point in a file

• Obtaining data for use with level metering

• Controlling playback level and positioning

AVAudioPlayer is an Objective-C-based framework and is useful for many tasks. In games, it is generally most useful for playing background music loops or sounds because it can cause latency when playing some spot effects.

For playing spot sound effects, you have a C-based framework called AudioToolbox. The part of this framework that is most interesting to play short sound effects is System Sound Services, in particular a method called AudioServicesPlaySystemSound.

You should be aware of some caveats when using System Sound Services. Only non-compressed file types are supported, such as CAF, AIF, or WAV files. Only playback sounds under 30 seconds in duration can be played. Level, positioning, and looping aren’t supported; and you can play only one system sound at a time. The major thing in favor of System Sound Services is that it has no latency; that is, sounds play instantly.

In Raiders, sound effects will be handled in the singleton class, SoundEffects. This corresponds to two methods: init and playExplosionEffect. Init initialises the system sounds, and playExplosionEffect performs the actual playback.

Because System Sound Services is a C-based framework, you cannot easily use familiar Objective-C methods with it. For example, to retrieve the sound from the main bundle and load the sound to a memory object, you must use some Core Foundation C methods:

CFBundleRef mainBundle = CFBundleGetMainBundle ();
    CFURLRef soundFileURLRef = CFBundleCopyResourceURL (mainBundle, CFSTR("bang2"), CFSTR("wav"), NULL);
// Create a system sound object representing the sound file
AudioServicesCreateSystemSoundID(soundFileURLRef, &explosionEffect);

explosionEffect is a variable that provides a link to the physical audio file. Playback is done using a single line of code:

AudioServicesPlaySystemSound(explosionEffect);


Another framework can be used in your games—OpenAL—which is a bit like OpenGL in that it isn’t an Apple-specific framework, and can be used on platforms other than Apple’s.

OpenAL offers the low latency of System Services, and also has the ability to play sounds spatially.

Sounds great, right? The caveat is that OpenAL can be hard to use—as in tearing-your-hair-out hard to use. As this is a beginning (and follicles-safe) book, we won’t discuss OpenAL.


To play the sound, playExplosionEffect is called from checkForCollisions in Level1SceneController.

The reason for making a SoundEffects singleton is that it makes it easier to swap one Audio framework for another, without changing the rest of the code. As long as you use the same method names, then if you decide System Sounds are what you want, you can swap in AVPlayer or some other framework that may be invented at some later date.

Wrapping Up

In this chapter, you learned the basics of collision detection and put them into practice. You also discovered some of the side effects of a collision. You used basic animation to create an explosion visual, and applied basic sound playback to add an explosion sound effect.

In the next chapter, you’ll add the finishing touches to Raiders.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.29.151