Chapter    8

Shoot ’em Up

What does a game of this kind need above all else? Something to shoot up and bullets to evade. In this chapter, you’ll be adding enemies to the game and even a boss monster.

Both enemies and player will use the new BulletCache class to shoot a variety of bullets from the same pool. The cache class reuses inactive bullets to avoid constantly allocating and releasing bullets from memory. Likewise, enemies will use their own EnemyCache class because they, too, will appear in greater numbers on the screen.

Obviously the player will be able to shoot these enemies. I also introduce the concept of component-based programming, which allows you to extend the game’s actors in a modular way. Besides shooting and moving components, you also create a healthbar component for the boss monster. After all, a boss monster should not be an ins tant kill but require several hits before it is destroyed.

Adding the BulletCache Class

The BulletCache class is the one-stop shop for creating new bullets in the ShootEmUp01 project. Previously all this code was in the GameLayer class, but it shouldn’t be the responsibility of the GameLayer to manage and create new bullets. Listing 8-1 shows the new BulletCache header file, and it now contains the CCSpriteBatchNode and the inactive bullets counter.

Listing 8-1.  The @interface of the BulletCache Class

#import < Foundation/Foundation.h>
#import "cocos2d.h"

@interface BulletCache : CCNode
{
    CCSpriteBatchNode* batch;
    NSUInteger nextInactiveBullet;
}

-(void) shootBulletFrom:(CGPoint)startPosition
                velocity:(CGPoint)velocity
                frameName:(NSString*)frameName;
@end

To refactor the bullet-shooting code out of the GameLayer class, you need to move both the initialization and the method to shoot bullets to the new BulletCache class.

Therefore remove the declaration and implementation of the shootBulletFromShip:(Ship*)ship method from the GameLayer class. Also remove the nextInactiveBullet instance variable from the interface and remove the bullet initialization code from the init method, as seen in Listing 8-2. All this code will be moved over to the new BulletCache class, the implementation of which is in Listing 8-4.

Listing 8-2.  Remove This Code from the GameLayer class’s init Method

// Now uses the image from the Texture Atlas.
CCSpriteFrame* bulletFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"bullet.png"];
CCSpriteBatchNode* batch = [CCSpriteBatchNode batchNodeWithTexture:bulletFrame.texture];
[self addChild:batch z:1 tag:GameSceneNodeTagBulletSpriteBatch];

// Create a number of bullets up front and re-use them whenever necessary.
for (int i = 0; i < 400; i++)
{
    Bullet* bullet = [Bullet bullet];
    bullet.visible = NO;
    [batch addChild:bullet];
}

// call bullet countrer from time to time
[self schedule:@selector(countBullets:) interval:3];

BulletCache* bulletCache = [BulletCache node];
[self addChild:bulletCache z:1 tag:GameSceneNodeTagBulletCache];

You should then add the initialization of the BulletCache class in place of the removed code from Listing 8-2 and also import the BulletCache.h header file at the top of the GameLayer.m implementation file.

Listing 8-3.  Import the BulletCache Header and Add the BulletCache Initialization to the init Method

#import"BulletCache.h"

. . .

-(id) init
{
    if ((self = [super init]))
    {
        . . .

        BulletCache* bulletCache = [BulletCache node];
        [self addChild:bulletCache z:1 tag:GameSceneNodeTagBulletCache];
    }
    return self;
}

You will also have to add GameSceneNodeTagBulletCache to the GameSceneNodeTags enum in the GameLayer.h file, and you have to add a bulletCache accessor method so other classes can access the BulletCache instance through the GameLayer. You can add this method just next to the defaultShip method:

-(BulletCache*) bulletCache
{
    CCNode* node = [self getChildByTag:GameSceneNodeTagBulletCache];
    NSAssert([node isKindOfClass:[BulletCache class]], @"not a BulletCache");
    return (BulletCache*)node;
}

Finally, this is the refactored GameLayer interface with the changes highlighted. Most notably a @class forward for the BulletCache class was added, and both defaultShip and bulletCache accessor methods are declared as properties, so from now on you can alternatively access them with dot notation.

. . .
@class Ship;
@class BulletCache;

@interface GameLayer : CCLayer
{
}

@property (readonly) Ship* defaultShip;
@property (readonly) BulletCache* bulletCache;

+(id) scene;
+(GameLayer*) sharedGameLayer;
-(CCSpriteBatchNode*) bulletSpriteBatch;
@end

For the new BulletCache class, I decided to keep the CCSpriteBatchNode in a member variable instead of using the CCNode getChildByTag method every time you need the sprite batch object. It’s a minor performance optimization. Because you’ll be adding the BulletCache class as child to the GameLayer, you can simply add the sprite batch node to the BulletCache class.

Note  There’s little harm in increasing the depth of the scene hierarchy by adding an in-between CCNode like BulletCache. If you’re concerned about scene hierarchy depth, the alternative would be to add the sprite batch node to the GameLayer class as usual and use an accessor method to get to the sprite batch node in the BulletCache class. But the additional function call overhead could possibly void any performance gain. If in doubt, always prefer to make your code more readable and then refactor later to improve performance where necessary, and only where necessary. And make no performance optimizations based on assumptions alone. Always measure performance before and after making supposedly performance-enhancing changes!

Listing 8-4.  The BulletCache Maintains a Pool of Bullets for Reuse

#import"BulletCache.h"
#import "Bullet.h"

@implementation BulletCache

-(id) init
{
    if ((self = [super init]))
    {
        // get any bullet image from the texture atlas we're using
        CCSpriteFrame* bulletFrame = [[CCSpriteFrameCache sharedSpriteFrameCache]←
        spriteFrameByName:@"bullet.png"];

        // use the bullet's texture
        batch = [CCSpriteBatchNode batchNodeWithTexture:bulletFrame.texture];
        [self addChild:batch];

        // Create a number of bullets up front and re-use them
        for (int i = 0; i < 200; i++)
        {
        Bullet* bullet = [Bullet bullet];
        bullet.visible = NO;
        [batch addChild:bullet];
        }
    }

    return self;
}

-(void) shootBulletFrom:(CGPoint)startPosition
        velocity:(CGPoint)velocity
        frameName:(NSString*)frameName
{
    CCArray* bullets = batch.children;
    CCNode* node = [bullets objectAtIndex:nextInactiveBullet];
    NSAssert([node isKindOfClass:[Bullet class]], @"not a Bullet!");

    Bullet* bullet = (Bullet*)node;
    [bullet shootBulletFrom:startPosition velocity:velocity frameName:frameName];

    nextInactiveBullet++;
    if (nextInactiveBullet > = bullets.count)
    {
        nextInactiveBullet = 0;
    }
}
@end

The shootBulletFrom method has changed the most, as you can see. It now takes three parameters—startPosition, velocity, and frameName—instead of a pointer to the Ship class. It then passes on these parameters to the Bullet class’s shootBulletFrom method, which I had to refactor as well. This is the new implementation that replaces the original shootBulletFrom method:

-(void) shootBulletFrom:(CGPoint)startPosition
     velocity:(CGPoint)vel
     frameName:(NSString*)frameName
{
    self.velocity = vel;
    self.position = startPosition;
    self.visible = YES;

    // change the bullet's texture by setting a different SpriteFrame to be displayed
    CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache]←
     spriteFrameByName:frameName];
    [self setDisplayFrame:frame];

    [self unscheduleUpdate];
    [self scheduleUpdate];
}

You also need to replace the method declaration in the Bullet.h interface. No surprises there:

-(void) shootBulletFrom:(CGPoint)startPosition
            velocity:(CGPoint)vel
          frameName:(NSString*)frameName;

Both velocity and position are now directly assigned to the bullet. This means the code calling the shootBulletFrom method has to determine the position, direction, and speed of the bullet. This is exactly what you want: full flexibility for shooting bullets, including changing the bullet’s sprite frame by using the setDisplayFrame method. Because the bullets are all in the same texture atlas and thus use the same texture, all it needs to do to change which bullet is displayed is set the desired sprite frame. In effect, this simply renders a different part of the texture and comes at no extra cost.

While in the Bullet class, I also fixed the boundary issues the bullets would have had—that only bullets moving outside the right-hand side of the screen would have been set invisible and put back on the waiting list. By using the CGRectIntersectsRect check with the bullet’s boundingBox and the screenRect in the update method, any bullet having moved completely outside the screen area will be marked for reuse. Shown in Listing 8-5 are the improved init and update methods of the Bullet class.

Listing 8-5.  The Bullet Class Code Is Changed to Test if a Bullet Left the Screen

static CGRect screenRect;
-(id) initWithBulletImage
{
    if ((self = [super initWithSpriteFrameName:@"bullet.png"]))
    {
        // make sure to initialize the screen rect only once
        if (CGRectIsEmpty(screenRect))
        {
        CGSize screenSize = [CCDirector sharedDirector].winSize;
        screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height);
        }
    }
    return self;
}

. . .

-(void) update:(ccTime)delta
{
    self.position = ccpAdd(self.position, ccpMult(velocity, delta));

    // When the bullet leaves the screen, make it invisible
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    CGRect screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height);
    if (CGRectIntersectsRect(self.boundingBox, screenRect) == NO)
    {
        self.visible = NO;
        [self unscheduleUpdate];
    }
}

The screenRect variable itself is now stored for convenience and performance reasons as a static variable, so it can be accessed by other instances of the Bullet class and doesn’t need to be re-created for each use. Static variables like screenRect are available in the class implementation file where they’re declared. They’re like global variables to the class; any class instance can read and modify the variable, as opposed to class member variables, which are local to every class instance. Because the screen size never changes during game play, and all Bullet instances need to use this variable, it makes sense to store it in a static variable for all class instances. The first bullet to be initialized sets the screenRect variable. The CGRectIsEmpty method checks whether the screenRect variable is still uninitialized; because the variable is static, it needs to be initialized only once.

In fact screenRect is useful to other classes, so it may be a good idea to move it to where you can access it from other classes. One way to do that is to add a property to the GameLayer class. You add the same init code as in Listing 8-5 to the GameLayer init method, and a method to return the static variable with the same name:

static CGRect screenRect;
-(CGRect) screenRect
{
    return screenRect;
}

And to the interface of GameLayer add the readonly property declaration for screenRect:

@property (readonly) CGRect screenRect;

By the way, whether screenRect is a static variable or an instance variable of the GameLayer class makes no difference in this instance. You can now import the GameLayer.h header file in another class and access screenRect via:

CGRect rect = [GameLayer sharedGameLayer].screenRect;

The InputLayer can now use the new BulletCache class and use it to shoot the player’s bullets. The bullet properties, such as starting position, velocity, and the sprite frame to use, are now passed by the shooting code in the update method of the InputLayer in Listing 8-6.

Listing 8-6.  The InputLayer Class Is Modified to Let the BulletCache Class Handle All the Action

#import"BulletCache.h"

. . .

-(void) update:(ccTime)delta
{
    totalTime + = delta;

    GameLayer* game = [GameLayer sharedGameLayer];
    Ship* ship = game.defaultShip;
    BulletCache* bulletCache = game.bulletCache;

    if (fireButton.active && totalTime > nextShotTime)
    {
        nextShotTime = totalTime + 0.4f;

        // Set the position, velocity and spriteframe before shooting
        CGPoint shotPos = CGPointMake(ship.position.x + 45, ship.position.y - 19);

        float spread = (CCRANDOM_0_1() - 0.5f) * 0.5f;
        CGPoint velocity = CGPointMake(200, spread * 50);
        [bulletCache shootBulletFrom:shotPos velocity:velocity frameName:@"bullet.png"];
    }

    // Allow faster shooting by quickly tapping the fire button.
    if (fireButton.active == NO)
    {
        nextShotTime = 0;
    }

    CGPoint velocity = ccpMult(joystick.velocity, 7000 * delta);
    ship.position = CGPointMake(ship.position.x + velocity.x * delta,
                                 ship.position.y + velocity.y * delta);
}

This short refactoring session adds much-needed flexibility to shooting bullets. I’m sure you can imagine how enemies can now use the very same code to shoot their own bullets.

Let’s Make Some Enemies

At this point, maybe you have only a fuzzy idea about what the enemies are, what they do, and what their behavior will be. That’s the thing with enemies—you never quite know what they’re up to.

In the case of games, that means going back to the drawing board, planning out what you want the enemies to do, and then deducing from that plan what you need to program. Contrary to real life, you have full control over the enemies. Doesn’t that make you feel powerful? But before you or anyone else can have fun, you need to come up with a plan for world domination.

I already created the graphics for three different types of enemies. At this point, I know only that at least one of them is supposed to be a boss monster. Take a look at Figure 8-1 and try to imagine what these enemies could be up to.

9781430244165_Fig08-01.jpg

Figure 8-1 .  The graphics used as the game’s enemy characters

Before you start programming, you should have a good understanding of which behaviors the enemies will have in common so that you program those parts only once. Eliminating code duplication is the single most important goal of clean code design.

Let’s see what we know for sure is common to all enemies:

  • Shoots bullets
  • Has logic that determines when and where to shoot what bullet
  • Can be hit by player’s bullets
  • Cannot be hit by other enemy’s bullets
  • Can take one or more hits (has health)
  • Has a specific behavior and movement pattern
  • Has a specific behavior or animation when destroyed
  • Will appear outside the screen area and move inside
  • Will not leave the screen area once inside

When you look at that list, you may notice that some of these attributes also apply to the player’s ship. It certainly can shoot bullets, we may want it to sustain multiple hits, and it should have a specific behavior or animation when destroyed. It makes sense to consider the player’s ship as just a special type of enemy and take it into consideration as well.

Looking at this feature set, I see three possible approaches. You could create one class that contains all the code for the ship, the enemies, and the boss monster. Certain parts of the code would run conditionally, depending on the type of enemy. For example, the shooting code may have different code paths for each type of game object. With a limited number of different objects, this approach works reasonably well—but it doesn’t scale. As you add more and more types of game objects, you end up with a bigger and fatter class containing all the game logic code. Any change to any part of that class has the potential to cause undesirable side effects in enemies’ or even the player ship’s behavior. Determining which code path to execute depending on a type variable is quite reminiscent of pure C programming and doesn’t make use of the object-oriented nature of Objective-C. But if used judiciously, it’s a very powerful concept even today.

The second approach is to create a class hierarchy with the Entity class as the base class and then derive a ship, two monsters, and a boss monster class from it. This is what a lot of programmers actually do, and it also works reasonably well for a small number of game objects. But in essence, it’s little different from the first approach, in that common code often ends up piling up in the base Entity class when it’s needed by some of the subclasses, but not all of them. It takes a turn for the worse as soon as that code in the Entity class starts to add switches based on the type of the enemy, to skip parts of the code or execute code paths specific to that type of enemy. That’s the same problem of the first C-style programming approach. With a little care, you can make sure the code specific to an enemy is part of that enemy’s class, but it’s all too easy to end up making most changes in the Entity class itself.

The third option is to use a component system, also called composition or aggregation. This means that individual code paths are separated from the class hierarchy and only added to the subclasses that need the components, such as a healthbar component. Because component-based development could be a book on its own and is likely overkill for a small project like this shoot-’em-up game, you’ll use a mixture of the class hierarchy approach and component design to at least give you an idea how compositing game objects out of individual parts works in principle and what the benefits are.

I do want to point out that there is no one best approach to code design. Certain choices are entirely subjective and depend on personal preferences and experience. Working code is often preferable to clean code if you’re willing to refactor your codebase often as you learn more about the game you’re making. Experience allows you to make more of these decisions up front in the planning stage and enables you to create more complex games faster. So if that’s your goal, start by making and completing smaller games and slowly push yourself to new limits and new challenges. It’s a learning process, and unfortunately the easiest way to kill your motivation is to be over-ambitious. There’s a reason why every seasoned game programmer will tell a beginner to start simple and to re-create classic arcade games like Tetris, Pac-Man, or Asteroids first.

Tip  About beginner’s enthusiasm: it’s good to have it. You’ll need it! But also be realistic about your expectations and what you’ll be able to pull off on your own or with a very small team. The web site “Your Game Idea Is Too Big” offers one very simple, fun, and thought-provoking way to make sure your game idea remains realistic. Test yourself: http://yourgameideaistoobig.com. Don’t miss the But. . . link at the bottom for excellent and inspirational advice.

The Enemy Class

Whether you create one class for each enemy type or write all the enemy code in one class depends a lot on the number of enemies. Typically neither approach is ideal because many classes may mean a lot of duplicate code or a deep class hierarchy. All enemy code in one class, however, can easily bloat that class and make it cumbersome to work with, especially if enemies have very diverse abilities. Using components to extend and modify classes is one solution to this problem, as I explain later. For now, because there will only be three enemy types and they aren’t very different in their behavior, a single class is just fine.

The Enemy class introduced in the ShootEmUp02 project derives from CCSprite for simplicity’s sake, and that will make it easier to work with sprite batching. And it declares an enum for the three enemy types that it should control. The interface in the header file is shown in Listing 8-7.

Listing 8-7.  The @interface of the Enemy Class

#import <Foundation/Foundation.h>
#import "cocos2d.h"

typedef enum
{
    EnemyTypeUFO = 0,
    EnemyTypeCruiser,
    EnemyTypeBoss,

    EnemyType_MAX,
} EnemyTypes;

@interface Enemy : CCSprite
{
    EnemyTypes type;
    int initialHitPoints;
    int hitPoints;
}

@property (readonly, nonatomic) int initialHitPoints;
@property (readonly, nonatomic) int hitPoints;

+(id) enemyWithType:(EnemyTypes)enemyType;
+(int) getSpawnFrequencyForEnemyType:(EnemyTypes)enemyType;
-(void) spawn;
@end

There is nothing too exciting here. The EnemyTypes enum is used to differentiate between the three different types of enemies currently supported, with EnemyType_MAX used as the upper limit for loops, as you’ll soon see. The Enemy class has a member variable that stores the type so that you can use switch statements to branch the code depending on the type of enemy as needed.

The implementation of Enemy contains a lot of code I’d like to discuss, so I’ll split the discussion into several topics and present only the relevant code, beginning with the initWithType method in Listing 8-8.

Listing 8-8.  Initializing an Enemy with a Type

#import "Enemy.h"
#import "GameLayer.h"
#import "StandardMoveComponent.h"
#import "StandardShootComponent.h"

. . .

@synthesize initialHitPoints, hitPoints;

-(id) initWithType:(EnemyTypes)enemyType
{

    type = enemyType;

    NSString* enemyFrameName;
    NSString* bulletFrameName;
    float shootFrequency = 6.0f;
    initialHitPoints = 1;

    switch (type)
    {
        case EnemyTypeUFO:
            enemyFrameName = @"monster-a.png";
            bulletFrameName = @"shot-a.png";
            break;
        case EnemyTypeCruiser:
            enemyFrameName = @"monster-b.png";
            bulletFrameName = @"shot-b.png";
            shootFrequency = 1.0f;
            initialHitPoints = 3;
            break;
        case EnemyTypeBoss:
            enemyFrameName = @"monster-c.png";
            bulletFrameName = @"shot-c.png";
            shootFrequency = 2.0f;
            initialHitPoints = 15;
            break;

            default:
            [NSException exceptionWithName:@"Enemy Exception"
                                reason:@"unhandled enemy type"
            userInfo:nil];
    }

    self = [super initWithSpriteFrameName:enemyFrameName];
    if (self)
    {
            // Create the game logic components
            [self addChild:[StandardMoveComponent node]];

            StandardShootComponent* shootComponent = [StandardShootComponent node];
            shootComponent.shootFrequency = shootFrequency;
            shootComponent.bulletFrameName = bulletFrameName;
            [self addChild:shootComponent];

            // enemies start invisible
            self.visible = NO;

            [self initSpawnFrequency];
    }
    return self;
}

+(id) enemyWithType:(EnemyTypes)enemyType
{
    return [[self alloc] initWithType:enemyType];
}

The code begins by setting variables, depending on the enemy type, using a switch statement to provide default values for each type of enemy—the sprite frame name to use, the name of the bullet sprite frame, and the shooting frequency. The default case of the switch statement throws an exception because that option usually results from adding a new enemy type to the EnemyTypes enum without extending this switch statement accordingly. Safeguarding your switch statements in this way so that no default case will be accepted is a good strategy to avoid spending too much debugging time on simple human errors. And you are a human being, right? So, you’re prone to forget these things. I know I am. Instead of wondering why your new enemy doesn’t move or shoots the wrong bullets, you’ll get a crash that waves a big red flag saying, “Hey, you forgot to update me!”

It’s also perfectly fine to run code before the assignment to self, as long as you don’t forget to call a [super init. . .] method eventually. Otherwise, the superclass won’t be properly initialized, and that can lead to strange bugs and crashes.

The component classes created and added to Enemy contain exchangeable code used to extend the behavior and looks of the enemies. I’ll get to components soon; for the moment, just know that the StandardMoveComponent class allows the enemy to move, whereas the StandardShootComponent allows it to—you guessed it—shoot. The necessary import statements are at the beginning of Listing 8-8.

Focus your attention now on the initSpawnFrequency method in the Enemy class. The relevant code is shown in Listing 8-9.

Listing 8-9.  Controlling the Spawning of Enemies

staticNSMutableArray* spawnFrequency = nil;

-(void) initSpawnFrequency
{
    // initialize how frequently the enemies will spawn
    if (spawnFrequency == nil)
    {
        spawnFrequency = [NSMutableArray arrayWithCapacity:EnemyType_MAX];
        [spawnFrequency insertObject:[NSNumber numberWithInt:80]
                                                     atIndex:EnemyTypeUFO];
        [spawnFrequency insertObject:[NSNumber numberWithInt:260]
                                                     atIndex:EnemyTypeCruiser];
        [spawnFrequency insertObject:[NSNumber numberWithInt:1500]
                                                     atIndex:EnemyTypeBoss];

        // spawn one enemy immediately
        [self spawn];
    }
}

+(int) getSpawnFrequencyForEnemyType:(EnemyTypes)enemyType
{
    NSAssert(enemyType < EnemyType_MAX, @"invalid enemy type");
    NSNumber* number = [spawnFrequency objectAtIndex:enemyType];
    return number.intValue;
}

You store the spawn frequency values for each type of enemy in a static spawnFrequency NSMutableArray. It’s a static variable because the spawn frequency isn’t needed for each enemy but only for each enemy type. The first Enemy instance that executes the initSpawnFrequency method will find that the spawnFrequency NSMutableArray is nil and so initializes it.

Because an NSMutableArray can store only objects and not primitive data types like integers, the values have to be wrapped into an NSNumber class using the numberWithInt initializer. I chose to use insertObject here instead of addObject because it not only ensures that the values will have the same index as the enemy type defined in the enum, but it also tells any other programmer looking at this code that the index used has a meaning. In this case, the index is synonymous with the enemy type. Although it’s technically unnecessary to specify the index here, it helps to show which value is used for which enemy type.

Because you’re using ARC, you don’t need to consider freeing the memory of the spawnFrequency array. Its memory will be released when it’s no longer used. Which in the case of static variables is when the app shuts down.

Note  This reminds me of a misconception about memory leaks—fact is, when an iOS app is leaking memory, then less free memory is available for the app, and that’s all there is to it. But when an app terminates or is terminated by the system, iOS frees all of the memory that was in use by the app, leaked or not. This behavior is unlike desktop operating systems. Memory leaks in iOS apps won’t affect other apps or decrease available memory for other apps if the leaking app was terminated.

The reason why rebooting your device helps free up memory is caused by either memory leaks in iOS itself or iOS loading additional subsystems and spawning new processes as part of the app’s interaction with iOS.

You spawn an enemy with the spawn method:

-(void) spawn
{
    // Select a spawn location just outside the right side of the screen
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    CGSize spriteSize = self.contentSize;
    float xPos = screenSize.width + spriteSize.width * 0.5f;
    float yPos = CCRANDOM_0_1() * (screenSize.height - spriteSize.height) +
     spriteSize.height * 0.5f;
    self.position = CGPointMake(xPos, yPos);

    // reset health
    hitPoints = initialHitPoints;

    // Finally set yourself to be visible, this also f lag the enemy as "in use"
    self.visible = YES;
}

Because an EnemyCache is used to create all instances of enemies up front, the whole spawning process is limited to choosing a random y position just outside the right-hand side of the screen and then setting the Enemy sprite to be visible. The visible status is used elsewhere in the project, specifically by component classes, to determine whether the Enemy is currently in use. If it’s not visible, it can be spawned to make it visible, but it should run its game logic code only while it’s visible.

The EnemyCache Class

I just mentioned the EnemyCache class, which is also new in the ShootEmUp02 project. By its name, it should remind you of the BulletCache class, which also holds a number of pre-initialized objects for fast and easy reuse. This avoids creating and releasing objects during game play that can be a source of minor performance hiccups. Especially for action games, those small glitches can have a devastating effect on the player’s experience. With that said, look at the unspectacular header file of the EnemyCache in Listing 8-10.

Listing 8-10.  The @interface of the EnemyCache Class

#import < Foundation/Foundation.h>
#import "cocos2d.h"

@interface EnemyCache : CCNode
{
    CCSpriteBatchNode* batch;
    NSMutableArray* enemies;

    int updateCount;
}
@end

After the spriteBatch, which contains all the enemy sprites, there’s an enemies NSMutableArray that stores a list of enemies of each type. The updateCount variable is increased every frame and used to spawn enemies at regular intervals. The init method of the EnemyCache is quite similar to the BulletCache init with its initialization of the CCSpriteBatchNode:

-(id) init
{
    if ((self = [super init]))
    {
        // get any image from the texture atlas we're using
        CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache]←
        spriteFrameByName:@"monster-a.png"];
        batch = [CCSpriteBatchNode batchNodeWithTexture:frame.texture];
        [self addChild:batch];
        [self initEnemies];
        [self scheduleUpdate];
    }

    return self;
}

But because the code for initializing the enemies is a bit more complex, I extracted it into its own method, as shown in Listing 8-11.

Listing 8-11.  Initializing the Pool of Enemies for Later Reuse

-(void) initEnemies
{
    // create the enemies array containing further arrays for each type
    enemies = [NSMutableArray arrayWithCapacity:EnemyType_MAX];

    // create the arrays for each type
    for (NSUInteger i = 0; i < EnemyType_MAX; i++)
    {
     // depending on enemy type the array capacity is set
     // to hold the desired number of enemies
     NSUInteger capacity;
     switch (i)
     {
         case EnemyTypeUFO:
            capacity = 6;
            break;
         case EnemyTypeCruiser:
            capacity = 3;
            break;
         case EnemyTypeBoss:
            capacity = 1;
            break;

         default:
            [NSException exceptionWithName:@"EnemyCache Exception"
                                   reason:@"unhandled enemy type"
                                userInfo:nil];
            break;
            }

     // no alloc needed since the enemies array will retain anything added to it
     NSMutableArray* enemiesOfType = [NSMutableArray arrayWithCapacity:capacity];
     [enemies addObject:enemiesOfType];

     for (NSUInteger j = 0; j < capacity; j++)
     {
         Enemy * enemy = [Enemy enemyWithType:i];
         [batch addChild:enemy z:0 tag:i];
         [enemiesOfType addObject:enemy];
     }
    }
}

The interesting part here is that the NSMutableArray* enemies itself contains NSMutableArray* objects, one per enemy type. It’s what’s called a two-dimensional array—an array of arrays.

The initial capacity of each enemiesOfType NSMutableArray also determines how many enemies of that type can be on the screen at once. In this way, you can keep the maximum number of enemies on the screen under control. Each enemiesOfType NSMutableArray is then added to enemies NSMutableArray using addObject, just like any other object. If you want, you can create deep hierarchies in this way. As a matter of fact, the cocos2d node hierarchy is built on CCNode classes containing a children instance variable of an array that can contain more CCNode classes, and so on. I mentioned earlier that cocos2d uses the CCArray class internally. It’s an optimized replacement class for NSMutableArray, but as with all optimizations there are some drawbacks and compatibility issues. With NSMutableArray you’re always on the safe side.

Based on the initial capacity set for enemiesOfType, the desired number of enemies is created, added to the CCSpriteBatchNode, and also added to the corresponding enemiesOfType NSMutableArray. While the enemies could also be accessed through the CCSpriteBatchNode, keeping references to the enemy entities in separate arrays makes it easier to process them during later activities such as spawning, as shown in Listing 8-12.

Listing 8-12.  Spawning Enemies

-(void) spawnEnemyOfType:(EnemyTypes)enemyType
{
    NSMutableArray* enemiesOfType = [enemies objectAtIndex:enemyType];
    for (Enemy* enemy in enemiesOfType)
    {
     // find the first free enemy and respawn it
     if (enemy.visible == NO)
     {
     CCLOG(@"spawn enemy type %i", enemyType);
     [enemy spawn];
     break;
     }
    }
}

-(void) update:(ccTime)delta
{
    updateCount++;

    for (int i = (EnemyType_MAX – 1); i > = 0; i--)
    {
     int spawnFrequency = [Enemy getSpawnFrequencyForEnemyType:i];
     if (updateCount % spawnFrequency == 0)
     {
     [self spawnEnemyOfType:i];
     break;
     }
    }
}

The update method increases a simple update counter. It doesn’t take into effect the actual time passed, but because the variances are typically minimal, it’s a fair trade-off to make life a bit easier. This for loop oddly starts at (EnemyType_MAX – 1) and runs until i is negative. The only purpose for this is that higher-numbered EnemyTypes have spawn precedence over lower-numbered EnemyTypes. If a boss monster is scheduled to appear at the same time as a cruiser, the boss will be spawned. Otherwise, it could happen that the cruiser takes the boss’s spawn slot by trying to spawn at the same time, blocking the boss from ever spawning. It’s a side effect of the spawning logic, and I leave it up to you to extend and improve this code, because you’ll probably have to do anyway if you decide to write your own version of a classic shoot-’em-up game.

The spawnFrequency is obtained from Enemy’s getSpawnFrequencyForEnemyType method:

+(int) getSpawnFrequencyForEnemyType:(EnemyTypes)enemyType
{
    NSAssert(enemyType < EnemyType_MAX, @"invalid enemy type");
    NSNumber* number = [spawnFrequency objectAtIndex:enemyType];
    return number.intValue;
}

First the method asserts that the enemyType number is actually within the defined range. Then the NSNumber object for that enemy type is obtained and returned as intValue.

The modulo operator % returns the remainder left after the division of the two operands updateCount and spawnFrequency. This means an enemy is spawned only when updateCount can be evenly divided by spawnFrequency, resulting in a remainder of 0.

ThespawnEnemyOfType method then gets the NSMutableArray from the enemies NSMutableArray, which contains the list of enemiesOfType, another NSMutableArray. You can now iterate over only the desired enemy types, rather than having to go through all sprites added to the CCSpriteBatchNode. As soon as one enemy is found that isn’t visible, its spawn method is called. If all enemies of that type are visible, the maximum number of enemies is currently onscreen, and no further enemies are spawned, effectively limiting the number of enemies of a type on the screen at any time.

Finally, to have enemies appear on screen, open the GameLayer.m file and extend the init method with the code that initializes the EnemyCache. Add this code just above the BulletCache initialization, and don’t forget to import the EnemyCache.h header file at the top of GameLayer.m:

#import "EnemyCache.h
. . .
-(id) init
{
    if ((self = [super init]))
    {
     . . .

     EnemyCache* enemyCache = [EnemyCache node];
     [self addChild:enemyCache z:0];

     BulletCache* bulletCache = [BulletCache node];
     [self addChild:bulletCache z:1 tag:GameSceneNodeTagBulletCache];
    }
    return self;
}

The Component Classes

Component classes are intended as plug-ins for game logic. If you add a component to a node, the node will execute the behavior of the component: moving, shooting, animating, showing a healthbar, and so on. The big benefit is that you program these components to work generically and can use them with a great number of game objects. The components interact with their parent and should make as few assumptions about the parent class as possible. Of course, in some cases a component requires the parent to be a CCSprite or even an Enemy class, but then you can still use it with any type of CCSprite or Enemy class.

You can also configure component classes depending on the class that’s using the component. As an example for component classes, take a look at the StandardShootComponent initialization in the Enemy class:

StandardShootComponent* shootComponent = [StandardShootComponent node];
shootComponent.shootFrequency = shootFrequency;
shootComponent.bulletFrameName = bulletFrameName;
[self addChild:shootComponent];

The variables shootFrequency and bulletFrameName were set previously based on the EnemyType. By adding the StandardShootComponent to the Enemy class, the enemy will shoot specific bullets at certain intervals. Because this component makes no assumptions about the parent class, you can even add an instance of it to the Ship class and have your player’s ship shoot automatically at specific intervals! Or by simply activating and deactivating specialized shooting components, you can create the effect of changing weapons for the player with very little code. You just program the shooting code in isolation and then add it to a node and add some parameters to it. The only logic left for programming the switching of weapons is simply when to deactivate which components.

What’s more, you can reuse components in other games where they make sense, just by adding them to the new project. There should be no need to modify the component class behavior, therefore components are great for writing reusable code and are a standard mechanism in many, many game engines. To learn more about game components, see this blog post on my web site: www.learn-cocos2d.com/2010/06/prefer-composition-inheritance.

Look at the StandardShootComponent’s source code, starting with the header file:

#import < Foundation/Foundation.h>
#import "cocos2d.h"

@interface StandardShootComponent : CCSprite
{
    float updateCount;
    float shootFrequency;
    NSString* bulletFrameName;
}

@property (nonatomic) float shootFrequency;
@property (nonatomic) NSString* bulletFrameName;

@end

There’s one notable thing about this class. The StandardShootComponent inherits from CCSprite, even though it doesn’t use any texture, nor does it need to be displayed on screen in any way. That’s a workaround because all Enemy objects are added to a CCSpriteBatchNode, which can contain only CCSprite-based objects. This also extends to any child node of the Enemy class; thus, the StandardShootComponent needs to inherit from CCSprite to satisfy the requirement of the CCSpriteBatchNode.

The implementation of the StandardShootComponent is shown in Listing 8-13.

Listing 8-13.  The StandardShootComponent Implementation in Its Entirety

#import "StandardShootComponent.h"
#import "BulletCache.h"
#import "GameLayer.h"

@implementation StandardShootComponent

@synthesize shootFrequency;
@synthesize bulletFrameName;
 
-(id) init
{
    if ((self = [super init]))
    {
     [self scheduleUpdate];
    }

    return self;
}

-(void) update:(ccTime)delta
{
    if (self.parent.visible)
    {
     updateCount + = delta;

     if (updateCount > = shootFrequency)
     {
     updateCount = 0;

     GameLayer* game = [GameLayer sharedGameLayer];
     CGPoint startPos = ccpSub(self.parent.position, ←
     CGPointMake(self.parent.contentSize.width * 0.5f, 0));
     [game.bulletCache shootBulletFrom:startPos
     velocity:CGPointMake(−200, 0)
     frameName:bulletFrameName];
     }
    }
}
@end

The actual shooting code first checks whether the parent is visible, because if the parent isn’t visible, the code obviously shouldn’t shoot. The BulletCache is what shoots the bullet, using the bulletFrameName provided to the component and a fixed velocity. For the start position, the component’s position itself is irrelevant. Instead, the parent position and contentSize properties are used to calculate the correct starting position—in this case, at the left side of the enemy’s sprite.

This bullet startPos works reasonably well for regular enemies but may need tweaking for the boss enemy. I’ll leave it up to you to add another property to this component to set the bullet startPosition with. Alternatively, you could also create a separate BossShootComponent and add this only to boss enemies to create more complex shooting patterns. The same goes for StandardMoveComponents, which for the boss might require hovering at a certain position at the right-hand side of the screen.

Speaking of which, the interface for the StandardMoveComponent is pretty lightweight:

#import < Foundation/Foundation.h>
#import "cocos2d.h"

@interface StandardMoveComponent : CCSprite
{
    CGPoint velocity;
}
@end

And the implementation doesn’t do anything fancy either, as you can see in Listing 8-14.

Listing 8-14.  The Implementation of the StandardMoveComponent

#import"StandardMoveComponent.h"
#import "GameLayer.h"

@implementation StandardMoveComponent

-(id) init
{
    if ((self = [super init]))
    {
     velocity = CGPointMake(−100, 0);
     [self scheduleUpdate];
    }

    return self;
}

-(void) update:(ccTime)delta
{
    if (self.parent.visible)
    {
     CGSize screenSize = [CCDirector sharedDirector].winSize;
     if (self.parent.position.x > screenSize.width * 0.5f)
     {
     self.parent.position = ccpAdd(self.parent.position, ←
     ccpMult(velocity, delta));
     }
    }
}
@end

The StandardMoveComponent initializes its velocity so that whatever node is using it, it will move at a fixed speed to the left. The update method first checks whether the parent is visible, because if not, it’s implied that the parent is currently inactive. The parent is moved until it crosses the center position of the screen with the given velocity multiplied by the delta time, to move the same distance regardless of framerate.

As you can see, component code is usually very simple. That makes it easy to find and fix flaws in components as well. Over time, you can accumulate a number of such components for re-use in other apps, and you’ll need to write fewer of the commonly used code and more of the application-specific code—code that’s new and challenging and fun to write.

Shooting Things

I almost forgot—you actually want to shoot the enemies, right? Well, in the ShootEmUp03 project, you can!

The ideal starting point to check whether a bullet has hit something is in the BulletCache class. I’ve added just the method to do that. Actually, I’ve added three methods, two of them public; the third is private to the class and combines the common code (see Listing 8-15).

Listing 8-15.  Checking for Collisions with Bullets

-(BOOL) isPlayerBulletCollidingWithRect:(CGRect)rect
{
    return [self isBulletCollidingWithRect:rect usePlayerBullets:YES];
}

-(BOOL) isEnemyBulletCollidingWithRect:(CGRect)rect
{
    return [self isBulletCollidingWithRect:rect usePlayerBullets:NO];
}

-(BOOL) isBulletCollidingWithRect:(CGRect)rect usePlayerBullets:(bool)usePlayerBullets
{
    BOOL isColliding = NO;

    for (Bullet* bullet in batch.children)
    {
     if (bullet.visible && usePlayerBullets == bullet.isPlayerBullet)
     {
     if (CGRectIntersectsRect([bullet boundingBox], rect))
     {
         isColliding = YES;

         // remove the bullet
         bullet.visible = NO;
         break;
      }
     }
    }
    return isColliding;
}

You will also need to add the method declarations to the BulletCache interface:

-(BOOL) isPlayerBulletCollidingWithRect:(CGRect)rect;
-(BOOL) isEnemyBulletCollidingWithRect:(CGRect)rect;

The idea behind using the two wrapper methods, isPlayerBulletCollidingWithRect and isEnemyBulletCollidingWithRect, is to hide the internal detail of determining which kinds of bullets to use for collision checks. You could also expose the usePlayerBullets parameter to other classes, but doing so would only make it harder to eventually change the parameter from a BOOL to an enum, in case you want to introduce a third type of bullet.

Only visible bullets can collide, of course, and by checking the bullet’s new isPlayerBullet property, you ensure that enemies can’t shoot themselves. The actual collision test is a simple CGRectIntersectsRect test, and if the bullet has actually hit something, the bullet itself is also set to be invisible to make it disappear. The Bullet class is extended to include the isPlayerBullet property:

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@class Ship;

@interface Bullet : CCSprite
{
    CGPoint velocity;
    float outsideScreen;
    BOOL isPlayerBullet;
}

@property (readwrite, nonatomic) CGPoint velocity;
@property (readwrite, nonatomic) BOOL isPlayerBullet;

+(id) bullet;
-(void) shootBulletFrom:(CGPoint)startPosition
     velocity:(CGPoint)vel
     frameName:(NSString*)frameName
     isPlayerBullet:(BOOL)playerBullet;
@end

Accordingly the shootBulletFrom method in the Bullet class implementation has been extended to receive the isPlayerBullet parameter:

@synthesize velocity, isPlayerBullet;

. . .

-(void) shootBulletFrom:(CGPoint)startPosition
     velocity:(CGPoint)vel
     frameName:(NSString*)frameName
     isPlayerBullet:(BOOL)playerBullet;
{
    self.velocity = vel;
    self.position = startPosition;
    self.visible = YES;
    self.isPlayerBullet = playerBullet;
    . . .
}

In the exact same fashion, the shootBulletFrom method in the BulletCache class needs to be extended to receive the isPlayerBullet parameter and then pass it on to the Bullet class. Both InputLayer and StandardShootComponent make use of the shootBulletFrom method, and they need to pass in the isPlayerBullet parameter. The one in InputLayer sets it to YES, because that code is responsible for shooting with the ship. The StandardShootComponent sets isPlayerBullet to NO because the component is used for enemies.

The EnemyCache class holding all Enemy objects is the perfect place to call the new collision-detection methods to check whether any enemy was hit by a player bullet. The class has a new checkForBulletCollisions method, which is called from its update method as seen in Listing 8-16.

Listing 8-16.  Performing the Collision Checks in the EnemyCache Class

#import "BulletCache.h"
#import "GameLayer.h"

. . .
-(void) update:(ccTime)delta
{
    . . .

    [self checkForBulletCollisions];
}

-(void) checkForBulletCollisions
{
    for (Enemy* enemy in batch.children)
    {
        if (enemy.visible)
        {
            BulletCache* bulletCache = [GameLayer sharedGameLayer].bulletCache;
            CGRect bbox = enemy.boundingBox;
            if ([bulletCache isPlayerBulletCollidingWithRect:bbox])
            {
                // This enemy got hit . . .
                [enemy gotHit];
            }
        }
    }
}

Here again it’s convenient to be able to iterate over all the enemy entities in the game, skipping those that are currently not visible. Using each enemy’s boundingBox to check with the BulletCache isPlayerBulletCollidingWithRect method, you can quickly find whether an enemy got hit by a player bullet. If so, the Enemy method gotHit is called, which simply sets the enemy to be invisible for now if there are no more remaining hitpoints:

-(void) gotHit
{
    hitPoints--;
    if (hitPoints < = 0)
    {
        self.visible = NO;
    }
}

Consider it an exercise for you to implement the player’s ship being hit by enemy bullets. You’ll have to schedule an update method in Ship and then implement the checkForBulletCollisions method and call it from the update method. You’ll have to change the call to isPlayerBulletCollidingWithRect to isEnemyBulletCollidingWithRect and decide how you want to react to being hit by a bullet—for example, by playing a sound effect and maybe displaying a “game over” message.

A Healthbar for the Boss

The boss monster isn’t an easy, one-hit kill. It should give the player visual feedback about its health by displaying a healthbar that decreases with each hit. The first step toward a healthbar is utilizing the hitPoints member variable of the Enemy class, which tells you how many hits the enemy can take before being destroyed. The initialHitPoints variable stores the maximum hit points, because after an enemy gets killed you need to be able to restore the original hit points. You’re going to need both.

To display the healthbar, another component class is ideal because it provides a plug-and-play solution and can be re-used for other enemies if needed. The header file for the HealthbarComponent proves to be highly unspectacular:

#import < Foundation/Foundation.h>
#import "cocos2d.h"

@interface HealthbarComponent : CCSprite
{
}

-(void) reset;

@end

The implementation of the HealthbarComponent class is more interesting, as Listing 8-17 shows.

Listing 8-17.  The HealthBarComponent Updates Its scaleX Property Based on the Enemy’s Remaining Hit Points

#import"HealthbarComponent.h"
#import "Enemy.h"

@implementation HealthbarComponent

-(void) onEnter
{
    [super onEnter];
    [self scheduleUpdate];
}

-(void) reset
{
    float parentWidthHalf = self.parent.contentSize.width / 2;
    float parentHeight = self.parent.contentSize.height;
    float selfHeight = self.contentSize.height;
    self.position = CGPointMake(parentWidthHalf, parentHeight + selfHeight);
    self.scaleX = 1.0f;
    self.visible = YES;
}
-(void) update:(ccTime)delta
{
    if (self.parent.visible)
    {
     NSAssert([self.parent isKindOfClass:[Enemy class]], @"not a Enemy");
     Enemy* enemy = (Enemy*)self.parent;
     self.scaleX = enemy.hitPoints / (float)enemy.initialHitPoints;
    }
    else if (self.visible)
    {
     self.visible = NO;
    }
}
@end

The healthbar turns its visible state on and off to be in line with its parent Enemy object. The reset method places the healthbar sprite at the proper location just above the head of the Enemy’s sprite. Because the healthbar is a child of the enemy, it only needs to be offset from the enemy sprite’s 0,0 position (lower left-hand corner by default). And because a decrease in health is displayed by modifying the scaleX property, it, too, needs to be reset to its default scale.

In the update method, and only when its parent is visible, the HealthbarComponent first asserts that the parent is of the class Enemy. Because this component relies on certain properties only available in the Enemy class and its subclasses, you need to make sure that it’s the right parent class. Modify the scaleX property as a percentage of the current hit points divided by the initial hit points. Because there’s currently no way of telling when exactly the hit points changed, the calculation is done every frame, regardless of whether it’s needed. The overhead here is minimal, but for more complex calculations, a call to the HealthbarComponent from the onHit method of the Enemy class would be preferable.

Caution  The enemy.initialHitPoints divisor is cast to float. Otherwise, the division would be an integer division that would always result in 0 because integers can’t represent fractional numbers and are always rounded down. Casting the divisor to a f loat data type ensures that the division is done with floating-point precision, and therefore the result is a floating-point value.

In the init method of the Enemy class, the HealthbarComponent is added alongside the other components, but only if the enemy type is EnemyTypeBoss:

     // Create the game logic components
     [self addChild:[StandardMoveComponent node]];

     StandardShootComponent* shootComponent = [StandardShootComponent node];
     shootComponent.shootFrequency = shootFrequency;
     shootComponent.bulletFrameName = bulletFrameName;
     [self addChild:shootComponent];

     if (type == EnemyTypeBoss)
     {
        HealthbarComponent* healthbar = [HealthbarComponent •
        spriteWithSpriteFrameName:@"healthbar.png"];
        [self addChild:healthbar];
     }

In addition, the spawn method has been extended to include resetting the hit points to their initial values and calling the reset method of any possible HealthbarComponent added to the enemy. I omitted the explicit check if the enemy is a boss type here simply because the HealthbarComponent is universal and could be used by any type of enemy.

-(void) spawn
{
    // Select a spawn location just outside the right side of the screen
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    CGSize spriteSize = self.contentSize;
    float xPos = screenSize.width + spriteSize.width * 0.5f;
    float yPos = CCRANDOM_0_1() * (screenSize.height - spriteSize.height) +
        spriteSize.height * 0.5f;
    self.position = CGPointMake(xPos, yPos);

    // reset health
    hitPoints = initialHitPoints;

    // Finally set yourself to be visible, this also f lag the enemy as "in use"
    self.visible = YES;

    // reset certain components
    for (CCNode* node in self.children)
    {
        if ([node isKindOfClass:[HealthbarComponent class]])
        {
        HealthbarComponent* healthbar = (HealthbarComponent*)node;
        [healthbar reset];
        }
    }
}

You can now run the app and wait for a boss to appear. Each boss will have a red healthbar on top that decreases in width the more bullets it’s hit with.

Summary

Creating a complete and polished game is quite an effort—one that involves a lot of refactoring, changing working code to improve its design and to allow for more features existing in harmony with each other.

In this chapter, you learned the value of classes like BulletCache and EnemyCache, which manage all instances of certain classes so that you have easy access to them from one central point. And pooling objects together helps improve performance.

By using component classes and cocos2d’s node hierarchy to your advantage, you can create plug-and-play classes with very specific functionality. This helps you construct your game objects using composition rather than inheritance. It’s a much more flexible way to write game logic code and leads to better code reuse.

Finally, you learned how to shoot enemies and how the BulletCache and EnemyCache classes help perform such tasks in a straightforward manner. And the HealthbarComponent provided the perfect example of the component system at work.

The game to this point leaves a couple things open for you to build on. First and foremost, the player doesn’t get hit yet. And you might want to add a healthbar to the cruiser monster and write specialized move and shoot components for the boss monster’s behavior. Overall, it’s an excellent starting point for your own side-scrolling game, just waiting for you to improve on it.

In the next chapter, I show you how to add visual eye candy to the shoot-’em-up game by using particle effects.

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

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