Harder Monsters and More Levels: How To Make A Simple iPhone Game with Cocos2D 2.X Part 3

Ray Wenderlich

This post is also available in: Japanese

Watch out for the green guy!

Watch out for the green guy!

Note from Ray: You guys voted for me to update this classic beginning Cocos2D tutorial series from Cocos2D 1.X to Cocos2D 2.X in the weekly tutorial vote, so your wish is my command! :]

This tutorial series is now fully up-to-date for Cocos2D 2.X, Xcode 4.5, and has a ton of improvements such as Retina display and iPhone 4″ screen support. Here’s the pre Cocos2D 1.X version if you need it!

So far, the game you’ve been making in How To Make A Simple iPhone Game with Cocos2D 2.X tutorial series is pretty cool. You have a rotating turret, monsters to shoot, and uber sound effects.

But our turret has it too easy. The monsters only take one shot, and there’s just one level! He’s not even warming up yet.

In this tutorial, you will extend our project so that you make different types of monsters of varying difficulty, and implement multiple levels into the game.

Tougher Monsters

For fun, let’s create two types of monsters: a weak and fast monster, and a strong and slow monster. To help the player distinguish between the two, download these new resources for this tutorial, and add them to your project. It includes a new monster image and a cool explosion sound effect.

Now let’s make your Monster class. There are many ways to model your Monster class, but you’re going to do the simplest thing, which is to make our Monster class a subclass of CCSprite. You’re also going to create two subclasses of Monster: one for our weak and fast monster, and one for our strong and slow monster.

Note: Subclassing CCSprite like this works fine for simple games like this, but the more complicated your games get the more likely you are to run into maintainability issues long term when you do this. If your game is going to be complex, you might want to look into component based architecture instead.

Stay tuned – we will have a tutorial on this site on the subject soon! :]

Create a new file with the iOS\cocos2d v2.x\CCNode class template, make it a subclass of CCSprite, and name it Monster.m.

Then replace Monster.h with the following:

#import "cocos2d.h"
 
@interface Monster : CCSprite
 
@property (nonatomic, assign) int hp;
@property (nonatomic, assign) int minMoveDuration;
@property (nonatomic, assign) int maxMoveDuration;
 
- (id)initWithFile:(NSString *)file hp:(int)hp minMoveDuration:(int)minMoveDuration maxMoveDuration:(int)maxMoveDuration;
 
@end
 
@interface WeakAndFastMonster : Monster
@end
 
@interface StrongAndSlowMonster : Monster
@end

Pretty straightforward here: you just derive Monster from CCSprite and add a few variables for tracking monster state, and then derive two subclasses of Monster for two different types of monsters.

Now open up Monster.m and add in the implementation:

#import "Monster.h"
 
@implementation Monster
 
- (id)initWithFile:(NSString *)file hp:(int)hp minMoveDuration:(int)minMoveDuration maxMoveDuration:(int)maxMoveDuration {
    if ((self = [super initWithFile:file])) {
        self.hp = hp;
        self.minMoveDuration = minMoveDuration;
        self.maxMoveDuration = maxMoveDuration;
    }
    return self;
}
 
@end
 
@implementation WeakAndFastMonster
 
- (id)init {
    if ((self = [super initWithFile:@"monster.png" hp:1 minMoveDuration:3 maxMoveDuration:5])) {
    }
    return self;
}
 
@end
 
@implementation StrongAndSlowMonster
 
- (id)init {
    if ((self = [super initWithFile:@"monster2.png" hp:3 minMoveDuration:6 maxMoveDuration:12])) {
    }
    return self;
}
 
@end

This just defines the base class initializer, and the initializer for each of the subclasses, which set the default values for each type of monster.

Now let’s integrate our new Monster class into the rest of the code! First add the import to your new file to the top of HelloWorldLayer.m:

#import "Monster.h"

Then let’s modify the addMonster method to construct instances of our new class rather than creating the sprite directly. Replace the spriteWithFile line with the following:

//CCSprite * monster = [CCSprite spriteWithFile:@"monster.png"];
Monster * monster = nil;
if (arc4random() % 2 == 0) {
    monster = [[[WeakAndFastMonster alloc] init] autorelease];
} else {
    monster = [[[StrongAndSlowMonster alloc] init] autorelease];
}

This will give a 50% chance to spawn each type of monster. Also, since you’ve moved the speed of the monsters into the classes, modify the min/max duration lines as follows:

int minDuration = monster.minMoveDuration; //2.0;
int maxDuration = monster.maxMoveDuration; //4.0;

Finally, a couple mods to the updateMethod. First modify the code that loops through the monsters as follows:

BOOL monsterHit = FALSE;
NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
for (Monster *monster in _monsters) {
 
    if (CGRectIntersectsRect(projectile.boundingBox, monster.boundingBox)) {
        monsterHit = TRUE;
        monster.hp --;
        if (monster.hp <= 0) {
            [monstersToDelete addObject:monster];
        }
        break;
    }
}

So basically, instead of instantly killing the monster, you subtract an HP and only destroy it if it’s 0 or lower. Also, note that you break out of the loop if the projectile hits a monster, which means the projectile can only hit one monster per shot.

Finally, modify the projectilesToDelete test as follows:

if (monsterHit) {
    [projectilesToDelete addObject:projectile];
    [[SimpleAudioEngine sharedEngine] playEffect:@"explosion.caf"];
}

Build and run the code, and if all goes well you should see two different types of monsters running across the screen – which makes our turret’s life a bit more challenging!

Multiple type of monsters in the game!

Multiple Levels

In order to implement multiple levels, you need to create a class that keeps track all of the information that differentiates one level from another. For this simple game, you’ll just keep track of three things: the level number, how many seconds in between spawning enemies (harder levels will spawn monsters faster), and the background color (so you can easily differentiate levels).

To do this, create a new class with the iOS\Cocoa Touch\Objective-C class template. Name the class Level and make it a subclass of NSObject.

Then replace Level.h with the following:

#import <Foundation/Foundation.h>
#import "cocos2d.h"
 
@interface Level : NSObject
 
@property (nonatomic, assign) int levelNum;
@property (nonatomic, assign) float secsPerSpawn;
@property (nonatomic, assign) ccColor4B backgroundColor;
 
- (id)initWithLevelNum:(int)levelNum secsPerSpawn:(float)secsPerSpawn backgroundColor:(ccColor4B)backgroundColor;
 
@end

And Level.m with the following:

#import "Level.h"
 
@implementation Level
 
- (id)initWithLevelNum:(int)levelNum secsPerSpawn:(float)secsPerSpawn backgroundColor:(ccColor4B)backgroundColor {
    if ((self = [super init])) {
        self.levelNum = levelNum;
        self.secsPerSpawn = secsPerSpawn;
        self.backgroundColor = backgroundColor;
    }
    return self;
}
 
@end

As you can see, this is a very simple plain-old object that just keeps track of these three pieces of information.

Next, you need to create a class that keeps track of all of the levels, as well as which level you are currently on. To do this, create a new class with the iOS\Cocoa Touch\Objective-C class template. Name the class LevelManager and make it a subclass of NSObject.

Then replace LevelManager.h with the following:

#import <Foundation/Foundation.h>
#import "Level.h"
 
@interface LevelManager : NSObject
 
+ (LevelManager *)sharedInstance;
- (Level *)curLevel;
- (void)nextLevel;
- (void)reset;
 
@end

And replace LevelManager.m with the following:

#import "LevelManager.h"
 
@implementation LevelManager {
    NSArray * _levels;
    int _curLevelIdx;
}
 
+ (LevelManager *)sharedInstance {
    static dispatch_once_t once;
    static LevelManager * sharedInstance; dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
 
- (id)init {
    if ((self = [super init])) {
        _curLevelIdx = 0;
        Level * level1 = [[[Level alloc] initWithLevelNum:1 secsPerSpawn:2 backgroundColor:ccc4(255, 255, 255, 255)] autorelease];
        Level * level2 = [[[Level alloc] initWithLevelNum:2 secsPerSpawn:1 backgroundColor:ccc4(100, 150, 20, 255)] autorelease];
        _levels = [@[level1, level2] retain];
    }
    return self;
}
 
- (Level *)curLevel {
    if (_curLevelIdx >= _levels.count) {
        return nil;
    }
    return _levels[_curLevelIdx];
}
 
- (void)nextLevel {
    _curLevelIdx++;
}
 
- (void)reset {
    _curLevelIdx = 0;
}
 
- (void)dealloc {
    [_levels release];
    _levels = nil;
    [super dealloc];
}
 
@end

This is also a very simple class that keeps an array of the levels, as well as an index to the current level. It also contains a few helper methods to get the current level, advance to the next level, or reset back to the beginning.

Note that this class is a singleton, created/accessed through the static sharedInstance method.

Almost done – just need to refactor your code a bit to use this! Open HelloWorldLayer.m and make the following changes:

// Add to top of file
#import "LevelManager.h"
 
// In init, replace the call to super initWithColor with this:
if ((self = [super initWithColor:[LevelManager sharedInstance].curLevel.backgroundColor])) {
 
// In init, replace the line that sets up the gameLogic: selector with this:
[self schedule:@selector(gameLogic:) interval:[LevelManager sharedInstance].curLevel.secsPerSpawn];

Notice you are just reading the values from the current Level class rather than having them be the same hardcoded values for each level.

Finally open GameOverLayer.m and replace initWithWon: with this:

- (id)initWithWon:(BOOL)won {
    if ((self = [super initWithColor:ccc4(255, 255, 255, 255)])) {
 
        NSString * message;
        if (won) {
            [[LevelManager sharedInstance] nextLevel];
            Level * curLevel = [[LevelManager sharedInstance] curLevel];
            if (curLevel) {
                message = [NSString stringWithFormat:@"Get ready for level %d!", curLevel.levelNum];
            } else {
                message = @"You Won!";
                [[LevelManager sharedInstance] reset];
            }
        } else {
            message = @"You Lose :[";
            [[LevelManager sharedInstance] reset];
        }
 
        CGSize winSize = [[CCDirector sharedDirector] winSize];
        CCLabelTTF * label = [CCLabelTTF labelWithString:message fontName:@"Arial" fontSize:16];
        label.color = ccc3(0,0,0);
        label.position = ccp(winSize.width/2, winSize.height/2);
        [self addChild:label];
 
        [self runAction:
         [CCSequence actions:
          [CCDelayTime actionWithDuration:3],
          [CCCallBlockN actionWithBlock:^(CCNode *node) {
             [[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]];
        }],
          nil]];
    }
    return self;
}

Notice this moves to the next level or resets the game if appropriate, and displays a “get ready” string instead of the win/lose message if the player is advancing to the next level.

That’s it! Build and run, and see if you have what it takes to beat the game! :]

Multiple levels in the Cocos2D game

That’s A Wrap!

Here’s the final sample project with all of the code we’ve developed in this tutorial series.

You have a nice start to a game going here – a rotating turret, tons of enemies to shoot with varying qualities, multiple levels, win/lose scenes, and of course – awesome sound effects! ;]

Now that you know how to make a simple game, why not go a step further and check out some of our other Cocos2D tutorials? We have tutorials on making Tower Defense games, Beat ‘Em Up games, Platformer games, and much more! :]

I hope you enjoyed the series, and best of luck with your Cocos2D game projects!


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

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

38 Comments

[ 1 , 2 , 3 ]
  • Is it stopping at a breakpoint? Perhaps one was added accidentally (been there, done that!).
    Richard Caseyrcasey
  • Lol, I thought about that, but I have them disabled.
    rsh
  • Thanks for the clear tutorial!

    And I found a strange problem if using provided "monster2.png", and it works well if replace this file with others such as "player.png".

    I checked the file size, seems much bigger than "monster.png", is it anything wrong with the file format?
    journeyonmyway
  • FWIW, the problem was with my (id)init in Monster.m. For whatever reason, there was a conflict with CCSprite's init and I was getting into an infinite loop scenario. So I changed my signature to:

    -(id)initMonster

    And all works fine.
    rsh
  • Thank you all for making these great tutorials! they are really helpful to newcomers!
    ricolwang
  • i cant even get the first part to work. When i hit the target the projectile is not getting deleted.

    if([projectilesToDelete count] > 0){
    NSLog(@"projectiles to delete: %@", projectilesToDelete);
    NSLog(@"projectiles in total: %@", _projectiles);
    }
    for (CCSprite *projectile in projectilesToDelete) {
    NSLog(@"projectile loop where it is about to get deleted");
    [_projectiles removeObject:projectile];
    [self removeChild:projectile cleanup:YES];
    NSLog(@"projectile to delete: %@", projectilesToDelete);
    NSLog(@"projectiles in total: %@", _projectiles);
    }

    what this does is just print out the projectile before and after its supposed to get deleted, but it doesnt get deleted for some reason. The projectile is in the projectileToDelete array.
    someGuy1
  • How would I create levels with TMX files instead? Please help me, I have looked everywhere and can't figure it out.
    thompsonr394
  • thompsonr394 wrote:How would I create levels with TMX files instead? Please help me, I have looked everywhere and can't figure it out.

    There're some tutorials on the site:
    How To Make a Tile-Based Game
    Collisions and Collectables: How To Make a Tile Based Game with Cocos2D 2.X Part 2
    Cocos2D-X Tile Map Tutorial: Part 1
    Cocos2D-X Tile Map Tutorial: Part 2
    PoorTom
[ 1 , 2 , 3 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Thank you for voting! :]

    Total Voters: 601

    Loading ... Loading ...

Last week's winner: Best iOS Animations in 2014. [Read Now]!

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Jean-Pierre Distler
  • Barbara Reichart
  • Corinne Krych

... 49 total!

Update Team

  • Ray Fix
  • Andy Pereira

... 15 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Vitaliy Zarubin
  • Wilson Lin

... 32 total!

Subject Matter Experts

  • Richard Casey

... 4 total!