How To Make a Tower Defense Game Tutorial
This Tower Defense Game Tutorial shows you how to make a complete tower defense game with shooting towers and multiple waves of enemies. Now fully updated for Cocos2D 2.X! By .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Tower Wars: The Attack of the Towers
Towers in place? Check. Enemies advancing? Double check — and they look mean! Looks like it’s time to mow those suckers down! Here’s where the intelligence built into the tower code comes into play.
Each tower checks to see if there is an enemy within range. If so, the tower will start firing on the enemy until one of two things happen: either the enemy moves out of range, or the enemy gets destroyed. The tower then begins to look for another victim. :]
Pull it together, recruits! You’ve got a base to defend!
Because the Enemy and the Tower classes depend on each other, you have to update the headers for both classes first to prevent errors from displaying as you type in the implementation changes.
First, open Tower.h and make the following changes:
// Add some instance variables
BOOL attacking;
Enemy *chosenEnemy;
// Add method definition
-(void)targetKilled;
Open Enemy.h and make the following code changes:
// Add instance variable
NSMutableArray *attackedBy;
// Add method definitions
-(void)getAttacked:(Tower *)attacker;
-(void)gotLostSight:(Tower *)attacker;
-(void)getDamaged:(int)damage;
Next, go back and make the following changes to Tower.m:
// Import Enemy header at the top of the file:
#import "Enemy.h"
// Add the following methods
-(void)attackEnemy
{
[self schedule:@selector(shootWeapon) interval:fireRate];
}
-(void)chosenEnemyForAttack:(Enemy *)enemy
{
chosenEnemy = nil;
chosenEnemy = enemy;
[self attackEnemy];
[enemy getAttacked:self];
}
-(void)shootWeapon
{
CCSprite * bullet = [CCSprite spriteWithFile:@"bullet.png"];
[theGame addChild:bullet];
[bullet setPosition:mySprite.position];
[bullet runAction:[CCSequence actions:
[CCMoveTo actionWithDuration:0.1 position:chosenEnemy.mySprite.position],
[CCCallFunc actionWithTarget:self selector:@selector(damageEnemy)],
[CCCallFuncN actionWithTarget:self selector:@selector(removeBullet:)], nil]];
}
-(void)removeBullet:(CCSprite *)bullet
{
[bullet.parent removeChild:bullet cleanup:YES];
}
-(void)damageEnemy
{
[chosenEnemy getDamaged:damage];
}
-(void)targetKilled
{
if(chosenEnemy)
chosenEnemy =nil;
[self unschedule:@selector(shootWeapon)];
}
-(void)lostSightOfEnemy
{
[chosenEnemy gotLostSight:self];
if(chosenEnemy)
chosenEnemy =nil;
[self unschedule:@selector(shootWeapon)];
}
Finally, replace the empty update method with the following version:
-(void)update:(ccTime)dt {
if (chosenEnemy){
//We make it turn to target the enemy chosen
CGPoint normalized = ccpNormalize(ccp(chosenEnemy.mySprite.position.x-mySprite.position.x,
chosenEnemy.mySprite.position.y-mySprite.position.y));
mySprite.rotation = CC_RADIANS_TO_DEGREES(atan2(normalized.y,-normalized.x))+90;
if(![theGame circle:mySprite.position withRadius:attackRange
collisionWithCircle:chosenEnemy.mySprite.position collisionCircleRadius:1])
{
[self lostSightOfEnemy];
}
} else {
for(Enemy * enemy in theGame.enemies)
{
if([theGame circle:mySprite.position withRadius:attackRange
collisionWithCircle:enemy.mySprite.position collisionCircleRadius:1])
{
[self chosenEnemyForAttack:enemy];
break;
}
}
}
}
Yes, that's a lot of code :] Plus, you probably noticed some warnings from Xcode as you continued to add more code. First, sort out the warnings by adding a few final missing bits and the explanation as to what the above code does is below!
Make the following code changes in Enemy.m:
// Add the following at the beginning of initWithTheGame: (within the "if" condition)
attackedBy = [[NSMutableArray alloc] initWithCapacity:5];
// Replace the contents of getRemoved method with the following:
-(void)getRemoved
{
for(Tower * attacker in attackedBy)
{
[attacker targetKilled];
}
[self.parent removeChild:self cleanup:YES];
[theGame.enemies removeObject:self];
//Notify the game that we killed an enemy so we can check if we can send another wave
[theGame enemyGotKilled];
}
// Add the following methods
-(void)getAttacked:(Tower *)attacker
{
[attackedBy addObject:attacker];
}
-(void)gotLostSight:(Tower *)attacker
{
[attackedBy removeObject:attacker];
}
-(void)getDamaged:(int)damage
{
currentHp -=damage;
if(currentHp <=0)
{
[self getRemoved];
}
}
The most important part in the code is the update method in Tower. The Tower will check constantly to see if an enemy is within firing range. If so, then our tower rotates and begins to fire at the enemy.
Once an enemy is marked to be attacked, a method gets scheduled that fires a bullet at the initial interval of the tower's fire rate. In turn, each enemy holds a list of towers that are attacking it, so the towers can be signalled to stop firing if the enemy is killed.
Compile and run your app! Place a few towers on the map. You’ll see how the towers will start firing at enemies once they move into range, and the health bars for the enemies will decrease as they sustain more damage until they are finally eliminated! Victory is within reach!
Phew! Okay, there’s only a few details left to add until you are in possession of a fully-featured tower defence game! Sound effects would be a good touch. And although it’s nice to be invincible and filthy rich, your base should be capable of sustaining damage if it is hit by an enemy — and you need to limit the player’s gold supply.
The Shining Tower: Gotta Polish It All!
Start with displaying the player's remaining lives — and what happens when the player’s lives are gone!
Open HelloWorldLayer.h and add the following three instance variables:
int playerHp;
CCLabelBMFont *ui_hp_lbl;
BOOL gameEnded;
playerHp indicates how many lives the player has and CCLabelBMFont is a label that will display the count of lives. gameEnded is set once the game is over! Also add the following method definition:
-(void)doGameOver;
Now, open HelloWorldLayer.m and make the following changes:
// At the end of init, add the following lines of code:
// 7 - Player lives
playerHp = 5;
ui_hp_lbl = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"HP: %d",playerHp]
fntFile:@"font_red_14.fnt"];
[self addChild:ui_hp_lbl z:10];
[ui_hp_lbl setPosition:ccp(35,winSize.height-12)];
// Add the following methods
-(void)getHpDamage {
playerHp--;
[ui_hp_lbl setString:[NSString stringWithFormat:@"HP: %d",playerHp]];
if (playerHp <=0) {
[self doGameOver];
}
}
-(void)doGameOver {
if (!gameEnded) {
gameEnded = YES;
[[CCDirector sharedDirector]
replaceScene:[CCTransitionRotoZoom transitionWithDuration:1
scene:[HelloWorldLayer scene]]];
}
}
This adds a method that reduces the player's lives, updates the label, and checks to see if the player has run out of lives. If so, then the game is done!
The getHpDamage method gets called when an enemy reaches the base. You would need to add this to update: in Enemy.m to check what happens when the enemy has run out of waypoints to travel to. Fortunately, you already implemented this in an earlier code block, so you’re good to go! :]
Compile and run the game, but this time, try to reign in your trigger finger and let the enemies reach the end of the road.
You should see the player's lives reducing, until the game is lost.
All right, fat cat, time to limit that gold supply.
Most games implement the “zero-sum” feature by assigning a cost to each tower and giving the player limited resources. Your app will implement a similar model, but in a very simple fashion.
Open HelloWorldLayer.h and add the following instance variables:
int playerGold;
CCLabelBMFont *ui_gold_lbl;
As you did with the lives, add a variable to keep track of the gold and a label to display it. As well, add a new method definition:
-(void)awardGold:(int)gold;
Now, open HelloWorldLayer.m and do the following:
//Add the following method
-(void)awardGold:(int)gold {
playerGold += gold;
[ui_gold_lbl setString:[NSString stringWithFormat:@"GOLD: %d",playerGold]];
}
// Add at the end of init:
// 8 - Gold
playerGold = 1000;
ui_gold_lbl = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"GOLD: %d",playerGold]
fntFile:@"font_red_14.fnt"];
[self addChild:ui_gold_lbl z:10];
[ui_gold_lbl setPosition:ccp(135,winSize.height-12)];
[ui_gold_lbl setAnchorPoint:ccp(0,0.5)];
//Replace canBuyTower method with the following:
-(BOOL)canBuyTower {
if (playerGold - kTOWER_COST >=0)
return YES;
return NO;
}
// In ccTouchesBegan, add the following lines inside the if statement,
// where you commented that we would spend our gold later:
playerGold -= kTOWER_COST;
[ui_gold_lbl setString:[NSString stringWithFormat:@"GOLD: %d",playerGold]];
The new code above checks if there is enough gold every time the player tries to place a tower. If so, then the tower is placed and the cost of the tower is subtracted from the available gold. The player should get rewarded for their marksmanship, as well — award the player some gold each time they kill an enemy.
Add the following line to getDamaged: (inside the "if" condition) in Enemy.m:
[theGame awardGold:200];
Run the game now and you will notice that you can't place as many towers as before since each one costs some gold. Of course, killing enemies awards you gold so that you can keep buying more towers! It’s a wonderful system, isn’t it?
And now, finally, for some extra points, make your game a little more fun by adding some cool background music created by Kevin MacLeod and some sounds effects made with cxfr!
Open HelloWorldLayer.m and make the following changes:
//At the top of the file:
#import "SimpleAudioEngine.h"
//Inside init: (inside the "if" condition)
// 9 - sound
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"8bitDungeonLevel.mp3" loop:YES];
//Inside ccTouchesBegan, before instantiating a new Tower object:
[[SimpleAudioEngine sharedEngine] playEffect:@"tower_place.wav"];
//At the beginning of getHpDamage
[[SimpleAudioEngine sharedEngine] playEffect:@"life_lose.wav"];
Now, open Enemy.m and add the following lines:
//At the top of the file:
#import "SimpleAudioEngine.h"
//At the beginning of getDamaged:
[[SimpleAudioEngine sharedEngine] playEffect:@"laser_shoot.wav"];
That’s it — you’re totally, completely DONE! Compile and run the game and play around with it. Don’t you just love those retro sounds?