Chapter 8

Shoot 'emUp

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 will 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, we also create a healthbar component for the boss monster. After all, a boss monster should not be an instant 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 of this code was in the GameScene class, but it shouldn't be the responsibility of the GameScene 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;
    int nextInactiveBullet;
}

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

To refactor the bullet-shooting code out of the GameScene class, I needed to move both the initialization and the method to shoot bullets to the new BulletCache class (Listing 8–2). Along the way, I decided to keep the CCSpriteBatchNode in a member variable instead of using the CCNodegetChildByTag method every time I need the sprite batch object. It's a minor performance optimization. Since I'll be adding the BulletCache class as child to the GameScene, I 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 GameScene 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.

Listing 8–2. 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]images
            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) shootBulletAt:(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 shootBulletAt:startPosition velocity:velocity frameName:frameName];

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

The shootBulletAt 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 shootBulletAt method, which I had to refactor as well:

-(void) shootBulletAt:(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]images
        spriteFrameByName:frameName];
    [self setDisplayFrame:frame];

    [self unscheduleUpdate];
    [self scheduleUpdate];
}

Both velocity and position are now directly assigned to the bullet. This means that the code calling the shootBulletAt method has to determine the position, direction, and speed of the bullet. This is exactly what I wanted: full flexibility for shooting bullets, including changing the bullet's sprite frame by using the setDisplayFrame method. Since 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 to set the desired sprite frame. In effect, this is simply going to render a different part of the texture and comes at no extra cost.

While I was in the Bullet class, I also fixed the boundary issues the bullets would have had—that only bullets moving outside the right 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:

// When the bullet leaves the screen, make it invisible
if (CGRectIntersectsRect([self boundingBox], screenRect) == NO)
{
    …
}

The screenRect variable itself is now stored for convenience and performance reasons as a static variable, so it can be accessed by other classes and doesn't need to be re-created for each use. Static variables like screenRect are available in the class implementation file where they are declared. They are 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. Since 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; since the variable is static, I want to initialize it only once.

static CGRect screenRect;



// 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);
}

With these changes implemented, what's left is to clean up the GameScene by removing any of the methods and member variables previously used for shooting bullets. Specifically, I need to replace the CCSpriteBatchNode initialization with the initialization of the BulletCache class:

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

I also need to add a bulletCache accessor so other classes can access the BulletCache instance through the GameScene:

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

The InputLayer can now use the new BulletCache class and uses 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:

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

    GameScene* game = [GameScene sharedGameScene];
    Ship* ship = [game defaultShip];
    BulletCache* bulletCache = [game bulletCache];

    // 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 shootBulletAt:shotPos velocity:velocity frameName:@"bullet.png"];
}

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.

What About Enemies?

At this point, there's 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 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.

images

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. I 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 of 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 the Objective-C programming language. 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 is 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. This means that individual code paths are separated from the Entity class hierarchy and only added to the subclasses that need the components, such as a healthbar component. Since component-based development would justify a book on its own and is likely to be overkill for a small project like this shoot-'em-up game, I'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, enabling 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.

The Entity Class Hierarchy

For the base class, I created the Entity class in the ShootEmUp02 project. Entity is a generic class derived from CCSprite, which contains only the setPosition code of the Ship class to keep all instances of Entity inside the screen. I made just a small improvement to this code so that objects outside the screen area are allowed to move inside, but once inside, they can no longer leave the screen area. In this shoot-'em-up example game, the enemies won't pass by you; they'll stop mid-screen in order to illustrate the EnemyCache, introduced shortly. The screen area check simply checks whether the screen rectangle fully contains the sprite's boundingBox, and only if that's the case will the code to keep the sprite inside the screen rectangle run:

-(void) setPosition:(CGPoint)pos
{
    // If the current position is outside the screen no adjustments should be made!
    // This allows entities to move into the screen from outside.
    if ([self isOutsideScreenArea])
    {
        …
    }

    [super setPosition:pos];
}

The function isOutsideScreenArea is implemented as follows:

-(BOOL) isOutsideScreenArea
{
    return (CGRectContainsRect([GameScene screenRect], [self boundingBox]));
}

The Ship class has been replaced with ShipEntity. Since the Entity base class now contains the setPosition code, the only thing left in ShipEntity is the initWithShipImage method. It's the same as before, so I won't re-create it here.

The EnemyEntity Class

I do need to go more in depth with the EnemyEntity class and what it does, starting with the header file in Listing 8–3.

Listing 8–3. The @interface of the EnemyEntity Class

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

typedef enum
{
    EnemyTypeUFO = 0,
    EnemyTypeCruiser,
    EnemyTypeBoss,

    EnemyType_MAX,
} EnemyTypes;


@interface EnemyEntity : Entity
{
    EnemyTypes type;
}

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

There is nothing too exciting here. The EnemyTypesenum 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 see soon. The EnemyEntity class has a member variable that stores the type, so that I can use switch statements to branch the code depending on the type of enemy as needed.

The implementation of EnemyEntity 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–4.

Listing 8–4. Initializing an Enemy with a Type

-(id) initWithType:(EnemyTypes)enemyType
{
    type = enemyType;

    NSString* enemyFrameName;
    NSString* bulletFrameName;
    float shootFrequency = 6.0f;
    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;
            break;
        case EnemyTypeBoss:
            enemyFrameName = @"monster-c.png";
            bulletFrameName = @"shot-c.png";
            shootFrequency = 2.0f;
            break;

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

    if ((self = [super initWithSpriteFrameName:enemyFrameName]))
    {
        // 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;
}

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 EnemyTypesenum 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 it 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 EnemyEntity contain exchangeable code. I'll get to components soon; for the moment, just know that the StandardMoveComponent allows the enemy to move while the StandardShootComponent allows it to, you guessed it, shoot.

Let's focus our attention now on the initSpawnFrequency method. The relevant code is shown in Listing 8–5.

Listing 8–5. Controlling the Spawning of Enemies

static CCArray* spawnFrequency;

-(void) initSpawnFrequency
{
    // initialize how frequently the enemies will spawn
    if (spawnFrequency == nil)
    {
        spawnFrequency = [[CCArray alloc] initWithCapacity: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];
}

-(void) dealloc
{
    [spawnFrequency release];
    spawnFrequency = nil;

    [super dealloc];
}

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

Since a CCArray can storeonly 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.

Of course, the dealloc method releases the spawnFrequencyCCArray, and it also sets it to nil, which is very important. Being a static variable, the first EnemyEntity object to run its dealloc method will release the spawnFrequency's memory. If it wasn't set to nil immediately thereafter, the next EnemyEntity running its dealloc method would try the same and thus over-release the spawnFrequencyCCArray, leading to a crash. On the other hand, if the spawnFrequency variable is nil, any message sent to it, like the release message, will simply be ignored. I said it before, but it can't be repeated often enough: sending messages to nil objects is perfectly fine in Objective-C; the message will simply be ignored.

Spawning an entity is done by the spawn method:

-(void) spawn
{
    // Select a spawn location just outside the right side of the screen
    CGRect screenRect = [GameScene screenRect];
    CGSize spriteSize = [self contentSize];
    float xPos = screenRect.size.width + spriteSize.width * 0.5f;

    float yPos = CCRANDOM_0_1() * (screenRect.size.height - spriteSize.height) +images
        spriteSize.height * 0.5f;
    self.position = CGPointMake(xPos, yPos);

    // Finally set yourself to be visible, this also flag 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 side of the screen and then setting the EnemyEntity sprite to be visible. The visible status is used elsewhere in the project, specifically by component classes, to determine whether the EnemyEntity is currently in use. If it's not visible, it can be spawned to make it visible, but it should run its game logic codeonly while it's visible.

The EnemyCache Class

I just mentioned the EnemyCache class. 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 gameplay 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, let's look at the unspectacular header file of the EnemyCache in Listing 8–6.

Listing 8–6. The @interface of the EnemyCache Class

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

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

    int updateCount;
}
@end

After the spriteBatch, which contains all the enemy sprites, there's an enemiesCCArray 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 BulletCacheinit 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]images
            spriteFrameByName:@"monster-a.png"];
        batch = [CCSpriteBatchNode batchNodeWithTexture:frame.texture];
        [self addChild:batch];


        [self initEnemies];
        [self scheduleUpdate];
    }

    return self;
}

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

Listing 8–7. Initializing the Pool of Enemies for Later Reuse

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

    // create the arrays for each type
    for (int i = 0; i < EnemyType_MAX; i++)
    {
        // depending on enemy type the array capacity is set
        // to hold the desired number of enemies
        int 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
        CCArray* enemiesOfType = [CCArray arrayWithCapacity:capacity];
        [enemies addObject:enemiesOfType];

    }

    for (int i = 0; i < EnemyType_MAX; i++)
    {
        CCArray* enemiesOfType = [enemies objectAtIndex:i];
        int numEnemiesOfType = [enemiesOfType capacity];

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

-(void) dealloc
{
    [enemies release];
    [super dealloc];
}

The interesting part here is that the CCArray*enemies itself contains more CCArray* objects, one per enemy type. It's what's called a two-dimensional array. The member variable enemies requires the use of alloc, because otherwise its memory would be freed after leaving the initEnemies method. In contrast, the CCArray* objects added to the enemiesCCArray do not need to be created using alloc, because the enemiesCCArray retains the objects added to it.

The initial capacity of each enemiesOfTypeCCArray 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 enemiesOfTypeCCArray is then added to enemiesCCArray 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 CCArray*children member variables that can contain more CCNode classes, and so on.

I split the array initialization and the creation of enemies into separate loops even though both could be done in the same loop. They are simply different tasks and should be kept separate. The additional overhead of going through all enemy types once again is negligible.

Based on the initial capacity set in the CCArray initialization loop, the desired number of enemies is created, added to the CCSpriteBatchNode, and then added to the corresponding enemiesOfTypeCCArray. 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–8.

Listing 8–8. Spawning Enemies

-(void) spawnEnemyOfType:(EnemyTypes)enemyType
{
    CCArray* enemiesOfType = [enemies objectAtIndex:enemyType];

    EnemyEntity* enemy;
    CCARRAY_FOREACH(enemiesOfType, enemy)
    {
        // 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 = [EnemyEntity getSpawnFrequencyForEnemyType:i];

        if (updateCount % spawnFrequency == 0)
        {
            [self spawnEnemyOfType:i];
            break;
        }
    }
}

The update method increases a simple update counter. It does not take into effect the actual time passed, but since 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 sideeffect 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 EnemyEntity'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.

The spawnEnemyOfType method then gets the CCArray from the enemiesCCArray, which contains the list of enemiesOfType, another CCArray. 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 is not visible, its spawn method is called. If all enemies of that type are visible, the maximum number of enemies is currently on-screen and no further enemies are spawned, effectively limiting the number of enemies of a type on the screen at any time.

The Component Classes

Component classes are intended as plug-ins for game logic. If you add a component to an entity, the entity 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 almost automatically, because they interact with the parent CCNode class and should make as few assumptions about the parent class as possible. Of course, in some cases a component requires the parent to be an EnemyEntity class, but then you can still use it with any EnemyEntity.

Component classes can also be configured depending on the class that's using the component. As an example for component classes, let's take a look at the StandardShootComponent initialization in the EnemyEntity class:

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

The variables shootFrequency and bulletFrameName have been set previously based on the EnemyType. By adding the StandardShootComponent to the EnemyEntity class, the enemy will shoot specific bullets at certain intervals. Since this component makes no assumptions about the parent class, you could even add an instance of it to the ShipEntity 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 plug it into a game entity 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 these components in other games if they make sense. Components are great for writing reusable code and are a standard mechanism in many, many game engines. If you'd like to learn more about game components, please refer to this blog post on my web site: www.learn-cocos2d.com/2010/06/prefer-composition-inheritance/.

Let's 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, copy) NSString* bulletFrameName;

@end

There are two things of note. The first is that StandardShootComponent is derived from CCSprite, even though it doesn't use any texture. That is a workaround because all EnemyEntity objects are added to a CCSpriteBatchNode, which can contain only CCSprite- based objects. This also extends to any child node of the EnemyEntity class; thus, the StandardShootComponent needs to inherit from CCSprite to satisfy the requirement of the CCSpriteBatchNode.

Next there's an NSString* pointer, bulletFrameName, with a corresponding @property. If you look closely, you'll notice the keyword copy in the @property definition. That means assigning an NSString to this property will create a copy of it. This is important for strings since they are generally autoreleased objects, and we want to make sure this string is ours. We could also retain it, but the problem is that the source string could be an NSMutableString that can be changed. And that would also change the bulletFrameName, which would be undesirable in this case. Of course, with the copy keyword comes the responsibility to release the bulletFrameName memory on dealloc, as shown in the implementation in Listing 8–9.

Listing 8–9. The StandardShootComponent Implementation in Its Entirety

#import "StandardShootComponent.h"
#import "BulletCache.h"
#import "GameScene.h"

@implementation StandardShootComponent

@synthesize shootFrequency;
@synthesize bulletFrameName;

-(id) init
{
    if ((self = [super init]))
    {
        [self scheduleUpdate];
    }

    return self;
}

-(void) dealloc
{
    [bulletFrameName release];
    [super dealloc];
}

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

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

            GameScene* game = [GameScene sharedGameScene];

            CGPoint startPos = ccpSub(self.parent.position,images
                CGPointMake(self.parent.contentSize.width * 0.5f, 0));
            [game.bulletCache shootBulletFrom:startPos
                        velocity:CGPointMake(-200, 0)
                        rameName: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 with which to set the bullet startPosition. 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 side of the screen.

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 if 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–10). The idea behind using the two wrapper methods, isPlayerBulletCollidingWithRect and isEnemyBulletCollidingWithRect, is to hide the internal detail of determining which kinds of bullets are used 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.

Listing 8–10. 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;

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

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

    return isColliding;
}

Only visible bullets can collide, of course, and by checking the bullet's isPlayerBullet property, we 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 EnemyCache class holding all EnemyEntity objects is the perfect place to call this method to check whetherany enemy was hit by a player bullet. The class has a new checkForBulletCollisions method, which is called from its update method:

-(void) checkForBulletCollisions
{
    EnemyEntity* enemy;
    CCARRAY_FOREACH([batch children], enemy)
    {
        if (enemy.visible)
        {
            BulletCache* bulletCache = [[GameScene sharedGameScene] 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 BulletCacheisPlayerBulletCollidingWithRect method, we can quickly find if an enemy got hit by a player bullet. If so, the EnemyEntity method gotHit is called, which simply sets the enemy to be invisible.

I'll leave it as an exercise for you to implement the player's ship being hit by enemy bullets. You'll have to schedule an update method in ShipEntity 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.

A Healthbar for the Boss

The boss monster shouldn't be an easy, one-hit kill. It also should give the player feedback about its health by displaying a healthbar that decreases with each hit. The first step toward a healthbar is adding the hitPoints member variable to the EnemyEntity class, which tells us how many hits the enemy can take before being destroyed. The initialHitPoints variable stores the maximum hit points, since after an enemy gets killed we need to be able to restore the original hit points. This is the changed header file in the EnemyEntity class:

@interface EnemyEntity : Entity
{
    EnemyTypes type;
    int initialHitPoints;
    int hitPoints;
}

@property (readonly, nonatomic) int hitPoints;

To display the healthbar, a component class is ideal because it provides a plug-and-play solution. 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–11 shows.

Listing 8–11. The HealthBarComponent Updates Its scaleX Property Based on the Enemy's Remaining Hit Points

#import "HealthbarComponent.h"
#import "EnemyEntity.h"

@implementation HealthbarComponent

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

        self.visible = NO;
        [self scheduleUpdate];
    }

    return self;
}

-(void) reset
{
    float parentHeight = self.parent.contentSize.height;
    float selfHeight = self.contentSize.height;
    self.position = CGPointMake(self.parent.anchorPointInPixels.x,images
        parentHeight + selfHeight);
    self.scaleX = 1;
    self.visible = YES;
}

-(void) update:(ccTime)delta
{
    if (self.parent.visible)
    {
        NSAssert([self.parent isKindOfClass:[EnemyEntity class]], @"not a EnemyEntity");
        EnemyEntity* parentEntity = (EnemyEntity*)self.parent;
        self.scaleX = parentEntity.hitPoints / (float)parentEntity.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 EnemyEntity object. The reset method places the healthbar sprite at the proper location just above the head of the EnemyEntity's sprite. Since a decrease in health is displayed by modifying the scaleX property, it, too, needs to be reset to its default state.

In the update method and when its parent is visible, the HealthbarComponent first asserts that the parent is of class EnemyEntity. Since this component relies on certain properties only available in the EnemyEntity class and its subclasses, we need to make sure that it's the right parent class. We modify the scaleX property as a percentage of the current hit points divided by the initial hit points. Since there's currently no way of telling when the hit points change, 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 EnemyEntity class would be preferable.

NOTE: The parentEntity.initialHitPoints is cast to float. If I hadn't done this, the division would be an integer division that would always result in 0 since integers can't represent fractional numbers and are always rounded down. Casting the divisor to a float data type ensures that floating-point division is done and the desired result is returned.

In the init method of the EnemyEntity, the HealthbarComponent is added if the enemy type is EnemyTypeBoss:

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

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 entity. 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
    CGRect screenRect = [GameScene screenRect];
    CGSize spriteSize = [self contentSize];
    float xPos = screenRect.size.width + spriteSize.width * 0.5f;
    float yPos = CCRANDOM_0_1() * (screenRect.size.height - spriteSize.height) +images
        spriteSize.height * 0.5f;
    self.position = CGPointMake(xPos, yPos);

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

    // reset health
    hitPoints = initialHitPoints;

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

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.

The Entity class hierarchy serves as just one example of how you can divide your classes without requiring each game object to be its own class. 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'll show you how to add visual eyecandy 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
18.116.14.245