So far, the game we’ve been making in How To Make A Simple iPhone Game with Cocos2Dis pretty cool. We 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, we will extend our project so that we 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 this modified monster image and add it to your project. While you’re at it, download this explosion sound effect I made and add it to the project as well.
Now let’s make our Monster class. There are many ways to model your Monster class, but we’re going to do the simplest thing, which is to make our Monster class a subclass of CCSprite. We’re also going to create two subclasses of Monster: one for our weak and fast monster, and one for our strong and slow monster.
Go to File/New File, choose Cocoa Touch Class/Objective-C class, make sure Subclass of NSObject is selected, and click Next. Name the file Monster.m and make sure “Also create Monster.h” is checked.
Then replace Monster.h with the following:
#import "cocos2d.h" @interface Monster : CCSprite { int _curHp; int _minMoveDuration; int _maxMoveDuration; } @property (nonatomic, assign) int hp; @property (nonatomic, assign) int minMoveDuration; @property (nonatomic, assign) int maxMoveDuration; @end @interface WeakAndFastMonster : Monster { } +(id)monster; @end @interface StrongAndSlowMonster : Monster { } +(id)monster; @end |
Pretty straightforward here: we 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 @synthesize hp = _curHp; @synthesize minMoveDuration = _minMoveDuration; @synthesize maxMoveDuration = _maxMoveDuration; @end @implementation WeakAndFastMonster + (id)monster { WeakAndFastMonster *monster = nil; if ((monster = [[[super alloc] initWithFile:@"Target.png"] autorelease])) { monster.hp = 1; monster.minMoveDuration = 3; monster.maxMoveDuration = 5; } return monster; } @end @implementation StrongAndSlowMonster + (id)monster { StrongAndSlowMonster *monster = nil; if ((monster = [[[super alloc] initWithFile:@"Target2.png"] autorelease])) { monster.hp = 3; monster.minMoveDuration = 6; monster.maxMoveDuration = 12; } return monster; } @end |
The only real code here is two static methods we added to return instances of each class, set up with the default HP and move durations.
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 HelloWorldScene.m:
#import "Monster.h" |
Then let’s modify the addTarget method to construct instances of our new class rather than creating the sprite directly. Replace the spriteWithFile line with the following:
//CCSprite *target = [CCSprite spriteWithFile:@"Target.png" rect:CGRectMake(0, 0, 27, 40)]; Monster *target = nil; if ((arc4random() % 2) == 0) { target = [WeakAndFastMonster monster]; } else { target = [StrongAndSlowMonster monster]; } |
This will give a 50% chance to spawn each type of monster. Also, since we’ve moved the speed of the monsters into the classes, modify the min/max duration lines as follows:
int minDuration = target.minMoveDuration; //2.0; int maxDuration = target.maxMoveDuration; //4.0; |
Finally, a couple mods to the updateMethod. First add a boolean right before the declaration of targetsToDelete:
BOOL monsterHit = FALSE; |
Then, inside the CGRectIntersectsRect test, instead of adding the object immediately in targetsToDelete, add the following code:
//[targetsToDelete addObject:target]; monsterHit = TRUE; Monster *monster = (Monster *)target; monster.hp--; if (monster.hp <= 0) { [targetsToDelete addObject:target]; } break; |
So basically, instead of instantly killing the monster, we subtract an HP and only destroy it if it’s 0 or lower. Also, note that we 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"]; } |
Compile 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 Levels
In order to implement multiple level support, we need to do some refactoring first. The refactoring is all pretty simple but there is a lot of it, and including it all in this post would make for a long, boring post.
Instead, I’ll include some high level overview of what was done and refer you to the sample project for full details.
Abstract out a Level class. Currently, the HelloWorldScene hard-coded information about the “level” such as which monsters to spawn, how often to spawn them, etc. So the first step is to pull out some of this information into a Level class so we can re-use the same logic in the HelloWorldScene for multiple levels.
Re-use scenes. Currently, we’re creating new instances of the scene class each time we switch between scenes. One of the drawbacks to this is that without careful management, you can incur delays as you load up your resources in the init method.
Since we have a simple game, what we’re going to do is just create one instance of each scene and just call a reset() method on it to clear out any old state (such as monsters or projectiles from the last level).
Use the app delegate as a switchboard. Currently, we don’t have any global state like what level we’re on or what the settings for that level are, and each scene just hard-codes which scenes it should switch to.
We’ll modify this so that the App Delegate stores pointers to the global state such as the level information, since it is a central place that is easy to access by all of the scenes. We’ll also put methods in the App Delegate to switch between scenes to centralize that logic and reduce intra-scene dependencies.
So those are the main points behind the refactoring – check out the sample project to see the full details. Keep in mind this is only one of many ways of doing it – if you have another cool way to organize the scene and game objects for your game please share!
So anyway, download the code and give it a whirl! We 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! ;]
That’s A Wrap!
Again, if you haven’t grabbed it already here’s the sample project with all of the code we’ve developed so far.
Now that you know how to make a simple game, why not go a step further and learn about how to make a tile-based game in Cocos2D! After all, who doesn’t like Ninjas eating watermelons?
I hope you enjoyed the series, and best of luck with your Cocos2D game projects!
Category: iPhone
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////