How To Make a Tower Defense Game Tutorial

Pablo Ruiz
Learn how to make a tower defense game!

Learn how to make a tower defense game!

Update 6/8/2013: Fully updated for Cocos2D 2.1-rc1 (original post by Pablo Ruiz, update by Brian Broom).

The tower defense genre is one of the most popular game genres in iOS – and for good reason. There’s something incredibly fun about building the ultimate defense and seeing it obliterate horders of invaders!

In this Tower Defense Game tutorial, you’ll get to build a tower defense game of your own from scratch, with Cocos2D!

Along the way, you’ll learn the following:

  • How to create waves of enemies with configurable spawn times.
  • How to make those enemies move along custom waypoints.
  • How to create towers on specific locations in the map.
  • How to make towers shoot at enemies.
  • How to visually debug the waypoints and attack ranges of towers.

At the end of the Tower Defense Game tutorial, you’ll have a solid framework for this type of game, which you’ll be able to expand upon to add new tower types, enemies, and maps!

For this tutorial, you’ll need a basic understanding of Cocos2D. If you are completely new to Cocos2D, you should check out some of the other Cocos2D tutorials on this site first.

Without further ado, let’s start building our defense!

A View from the Ivory Tower

If you’re not familiar with the genre, tower defense games are strategy games where you purchase and position armed towers at strategic points in order to stop waves of enemies that are trying to reach your base and destroy it.

Each wave of enemies is usually harder than the last, with stronger opponents who move more quickly and have greater resistance to your artillery. The game ends when you survive the waves through all of the levels (victory!) or when enough enemies reach your base to destroy it (Aaaugh! You’ve been defeated!).

Here’s a screenshot of how the game will look after you’re done:

As you can see in the image, enemies appear from the top left side of the screen, and they follow the green path to the player’s base.

Along the road, there are a number of platforms where the player can place a tower. The player can only purchase and place as many towers as they can afford, based on their gold reserves. The towers’ attack range is drawn as a white circle; if an enemy is within the tower’s range, the tower will fire on the enemy until the enemy is destroyed, or they move out of range of the tower.

Note: We have only provided resources for the iPhone (non-retina, non 4″ display), so be sure to use the normal iPhone simulator while going through this tutorial. Of course, if you use your own artwork the same techniques shown in this tutorial will apply to any type of iOS device or screen size.

Towers ‘R’ Us: Assembling the Resources

To get started quickly, I’ve created a starter project for you. It contains an empty Cocos2D project and most of the assets you will use in the tutorial.

So go ahead and download the starter project and extract it to your chosen project location.

Note: The art for the project comes from a free art pack by Vicki, which in turn is based on another free art pack by Oray Studios. If you like this art style, the guys at Oray Studios are available for hire!

The starter project was created from a new project from the Cocos2D 2.1 (RC1) basic template, which provides you with a working project that has a HelloWorldLayer with a label in the middle of the screen. You won’t be using this label as you’ll create your own UI, but this gives you a basic starter Cocos2D project that you know works correctly.

Open the project in Xcode, and compile it and run it to make sure that everything is working. The starter project has removed the actual “Hello World” text from the HelloWorldLayer so you’ll only get a black screen if the project runs correctly. But if the project does compile and run, then you’re all set!

Take a look at the project structure. Inside the TowerDefense folder, you’ll find:

  • The libs folder containing all the Cocos2D files
  • The Resources folder containing all the graphics and sounds

Now, you can set up the map and start creating the towers!

Someone to Tower Over Me: Placement

First, add the background image for the scene. Open HelloWorldLayer.m and add the following lines of code inside the “if” condition in init:

// 1 - Initialize
self.touchEnabled = YES;
CGSize winSize = [CCDirector sharedDirector].winSize;
// 2 - Set background        
CCSprite * background = [CCSprite spriteWithFile:@"bg.png"];
[self addChild:background];
[background setPosition:ccp(winSize.width/2,winSize.height/2)];

The first line in section #1 will allow the layer to receive touch events. The remainder of the code in section #2 adds a background sprite to our scene.

With the background in place, you can visually identify where the player is allowed to place towers. Now you need to set some points along the road where the player will be able to touch and build a tower. (Hey – you got a building permit for that, buddy?)

To keep things manageable, a .plist file will be used to store the placement points for the towers so they can be easily changed. TowersPosition.plist has been included in the Resources folder, which already has some tower positions in it.

Inspect this file; you’lll find an array of dictionaries which contain just 2 keys: “x” and “y”. Each dictionary entry represents a tower position by its x and y coordinates. Now you need to read in this file and place the bases for towers on the map!

Open HelloWorldLayer.h and add the following instance variable (within the curly braces after the @interface line):

NSMutableArray *towerBases;

Make the following changes in HelloWorldLayer.m:

//Add a new method 
-(void)loadTowerPositions
{
    NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"TowersPosition" ofType:@"plist"];
    NSArray * towerPositions = [NSArray arrayWithContentsOfFile:plistPath];
    towerBases = [[NSMutableArray alloc] initWithCapacity:10];
 
    for(NSDictionary * towerPos in towerPositions)
    {
        CCSprite * towerBase = [CCSprite spriteWithFile:@"open_spot.png"];
        [self addChild:towerBase];
        [towerBase setPosition:ccp([[towerPos objectForKey:@"x"] intValue],
                                   [[towerPos objectForKey:@"y"] intValue])];
        [towerBases addObject:towerBase];
    }
 
}
 
//In init, call this new method in section #3
// 3 - Load tower positions
[self loadTowerPositions];

Compile and run the app, and you will see squares on the sides of the path. These serve as the bases for the player’s towers.

Now that the tower bases are ready, call in the construction equipment and build some towers!

First, open HelloWorldLayer.h and add a property (after the closing curly brace):

@property (nonatomic,strong) NSMutableArray *towers;

Synthesize the towers property in HelloWorldLayer.m below the @implementation line:

@synthesize towers;

Now, create a new class to represent the towers. Add a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Tower, and make it a subclass of CCNode.

Replace the contents of Tower.h with the following:

#import "cocos2d.h"
#import "HelloWorldLayer.h"
 
#define kTOWER_COST 300
 
@class HelloWorldLayer, Enemy;
 
@interface Tower: CCNode {
    int attackRange;
    int damage;
    float fireRate;
}
 
@property (nonatomic,weak) HelloWorldLayer *theGame;
@property (nonatomic,strong) CCSprite *mySprite;
 
+(id)nodeWithTheGame:(HelloWorldLayer*)_game location:(CGPoint)location;
-(id)initWithTheGame:(HelloWorldLayer *)_game location:(CGPoint)location;
 
@end

Now replace the contents of Tower.m with the following:

#import "Tower.h"
 
@implementation Tower
 
@synthesize mySprite,theGame;
 
+(id) nodeWithTheGame:(HelloWorldLayer*)_game location:(CGPoint)location
{
    return [[self alloc] initWithTheGame:_game location:location];
}
 
-(id) initWithTheGame:(HelloWorldLayer *)_game location:(CGPoint)location
{
	if( (self=[super init])) {
 
		theGame = _game;
        	attackRange = 70;
       		damage = 10;
        	fireRate = 1;
 
        	mySprite = [CCSprite spriteWithFile:@"tower.png"];
		[self addChild:mySprite];
 
        	[mySprite setPosition:location];
 
        	[theGame addChild:self];
 
        	[self scheduleUpdate];
 
	}
 
	return self;
}
 
-(void)update:(ccTime)dt
{
 
}
 
-(void)draw
{
    ccDrawColor4B(255, 255, 255, 255);
    ccDrawCircle(mySprite.position, attackRange, 360, 30, false);
    [super draw];
}
 
@end

The Tower class contains several properties: a sprite, which is the visual representation of the tower; a reference to the parent layer for easy access; and three variables:

  • attackRange: Determines the distance at which the tower can attack enemies
  • damage: Determines how much damage this tower inflicts on enemies
  • fireRate: Determines how much time it takes the tower to reload and fire again.

With those three variables alone, you can create a wide range of different towers with varying attack properties, such as long-range heavy hitters that take a long time to reload, or eager-beaver snipers that fire quickly but have limited range.

Finally, the code contains a draw method which draws a circle around the tower showing its attack range which will be useful for debug purposes.

It’s time to let the player add some towers!

Open HelloWorldLayer.m and make the following changes:

//At the top of the file:
#import "Tower.h"
 
//Add the following methods:
-(BOOL)canBuyTower
{
    return YES;
}
 
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 
	for( UITouch *touch in touches ) {
		CGPoint location = [touch locationInView: [touch view]];
 
		location = [[CCDirector sharedDirector] convertToGL: location];
 
       	 	for(CCSprite * tb in towerBases)
        	{
			 if( CGRectContainsPoint([tb boundingBox],location) &&
                             [self canBuyTower] && !tb.userData)
			{
				 //We will spend our gold later.
 
                		Tower * tower = [Tower nodeWithTheGame:self location:tb.position];
                		[towers addObject:tower];
               			 tb.userData = (__bridge void *)(tower);
			}
		}
	}
}

ccTouchesBegan: detects when the user touches any point on the screen. The code then iterates through the towerBases array and checks if any tower base contains the point where the user touched the screen.

But before a tower can be created, you need to check two things!

  1. Can the player afford such a luxury? The canBuyTower method will check if the player has enough gold to buy the tower. For the moment though, your player has all the gold in Fort Knox, and canBuyTower always returns YES.
  2. Is the player violating any building codes? If tb.UserData is set, then there is already a tower on this base and you can’t add a new one!

If everything passes inspection, create a new tower, place it on the base, and add it to the towers array.

Note: The bridge statement towards the end of the method deserves some explanation. The starter project you downloaded has been set up to enable ARC for the files you are working with, but not all of Cocos2D. When you store a pointer to our (ARC) Tower object in the (non-ARC) property of CCSprite, the __bridge directive tells the compiler to simply store the pointer, and not transfer ownership. More details can be found here.

Compile and run the game. Touch any base and see that a tower is added along with a white circle around it, showing its attack range! Muahahaha, you’re armed and dangerous now!

But what good is all this firepower without any bad guys? Let’s invite some enemies to the party!

Tower Politics: Enemies, Waves and Waypoints

Before creating the enemies, let’s “pave the road” for them. The enemies will follow a series of waypoints, which are simply interconnected points that define a path the enemies use to move around your world. Enemies will appear at the first waypoint, search for the next waypoint in the list, move to that spot, and repeat, until they reach the last waypoint in the list — your base! If the baddies reach your base, you’ll suffer damage.

Create the waypoint list by creating a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Waypoint, and make it a subclass of CCNode.

Replace the contents of Waypoint.h with the following:

#import "cocos2d.h"
#import "HelloWorldLayer.h"
 
@interface Waypoint: CCNode {
    HelloWorldLayer *theGame;
}
 
@property (nonatomic,readwrite) CGPoint myPosition;
@property (nonatomic,assign) Waypoint *nextWaypoint;
 
+(id)nodeWithTheGame:(HelloWorldLayer*)_game location:(CGPoint)location;
-(id)initWithTheGame:(HelloWorldLayer *)_game location:(CGPoint)location;
 
@end

Now, replace Waypoint.m with the following code:

#import "Waypoint.h"
 
@implementation Waypoint
 
@synthesize myPosition, nextWaypoint;
 
+(id)nodeWithTheGame:(HelloWorldLayer*)_game location:(CGPoint)location
{
    return [[self alloc] initWithTheGame:_game location:location];
}
 
-(id)initWithTheGame:(HelloWorldLayer *)_game location:(CGPoint)location
{
	if( (self=[super init])) {
 
		theGame = _game;
 
        [self setPosition:CGPointZero];
        myPosition = location;
 
        [theGame addChild:self];
 
	}
 
	return self;
}
 
-(void)draw
{
    ccDrawColor4B(0, 255, 2, 255);
    ccDrawCircle(myPosition, 6, 360, 30, false);
    ccDrawCircle(myPosition, 2, 360, 30, false);
 
    if(nextWaypoint)
        ccDrawLine(myPosition, nextWaypoint.myPosition);
 
    [super draw];   
}
 
@end

First, the code initializes a waypoint object by passing in the HelloWorldLayer object by reference and a CGPoint, which is the position of the waypoint.

Each waypoint contains a reference to the next waypoint; this creates a linked list of waypoints (did you pay attention in data structures class?). Each waypoint “knows” the next waypoint in the list. This way, you can guide the enemies to their final destination by following the chain of waypoints. The enemies never retreat in this world; they’re little kamikaze warriors.

Finally, the draw method shows where the waypoint is placed, and draws a line connecting it with the next one, for debug purposes only. A production version of the game wouldn’t draw the enemies’ path – that would make it too easy for the player!

Create the list of waypoints. Open HelloWorldLayer.h and add the following property:

@property (nonatomic,strong) NSMutableArray *waypoints;

Next, add the following code to HelloWorldLayer.m:

//At the top of the file:
#import "Waypoint.h"
 
// Add synthesise
@synthesize waypoints;
 
//Add this method 
-(void)addWaypoints
{
    waypoints = [[NSMutableArray alloc] init];
 
    Waypoint * waypoint1 = [Waypoint nodeWithTheGame:self location:ccp(420,35)];
    [waypoints addObject:waypoint1];
 
    Waypoint * waypoint2 = [Waypoint nodeWithTheGame:self location:ccp(35,35)];
    [waypoints addObject:waypoint2];
    waypoint2.nextWaypoint =waypoint1;
 
    Waypoint * waypoint3 = [Waypoint nodeWithTheGame:self location:ccp(35,130)];
    [waypoints addObject:waypoint3];
    waypoint3.nextWaypoint =waypoint2;
 
    Waypoint * waypoint4 = [Waypoint nodeWithTheGame:self location:ccp(445,130)];
    [waypoints addObject:waypoint4];
    waypoint4.nextWaypoint =waypoint3;
 
    Waypoint * waypoint5 = [Waypoint nodeWithTheGame:self location:ccp(445,220)];
    [waypoints addObject:waypoint5];
     waypoint5.nextWaypoint =waypoint4;
 
    Waypoint * waypoint6 = [Waypoint nodeWithTheGame:self location:ccp(-40,220)];
    [waypoints addObject:waypoint6];
     waypoint6.nextWaypoint =waypoint5;
 
}
 
// At the end of init:
// 4 - Add waypoints
[self addWaypoints];

Compile and run the game. You’ll see the following:

There are six waypoints on the map; this is the path the enemies will follow. Before you let the baddies loose in your game, you need to add a helper method.

First, add the method definition to the header file so that other classes can access this method without compiler warnings.

Open HelloWorldLayer.h and add the following method definitions before the @end:

-(BOOL)circle:(CGPoint)circlePoint withRadius:(float)radius 
       collisionWithCircle:(CGPoint)circlePointTwo collisionCircleRadius:(float)radiusTwo;
void ccFillPoly(CGPoint *poli, int points, BOOL closePolygon);
-(void) enemyGotKilled;
-(void) getHpDamage;

Next, open HelloWorldLayer.m and add the following method (before the @end):

-(BOOL)circle:(CGPoint) circlePoint withRadius:(float) radius 
       collisionWithCircle:(CGPoint) circlePointTwo collisionCircleRadius:(float) radiusTwo {
    float xdif = circlePoint.x - circlePointTwo.x;
    float ydif = circlePoint.y - circlePointTwo.y;
 
    float distance = sqrt(xdif*xdif+ydif*ydif);
 
    if(distance <= radius+radiusTwo) 
        return YES;
 
    return NO;
}

The collisionWithCircle method will help us determine when two circles collide, or intersect. This will help determine if an enemy reached a waypoint, along with detecting enemies that are within a tower’s attack range.

Time to add the baddies to the mix!

Open HelloWorldLayer.h and add the following code:

// Add these instance variables
int wave;
CCLabelBMFont *ui_wave_lbl;
 
// Add the following property to the properties section
@property (nonatomic,strong) NSMutableArray *enemies;

Make the following change in HelloWorldLayer.m:

// Synthesize enemies
@synthesize enemies;

Time to create a class that will hold the enemy’s information and manage how they move on screen. Create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Enemy, and make it a subclass of CCNode.

Replace the contents of Enemy.h with the following:

#import "cocos2d.h"
#import "HelloWorldLayer.h"
 
@class HelloWorldLayer, Waypoint, Tower;
 
@interface Enemy: CCNode {
    CGPoint myPosition;
    int maxHp;
    int currentHp;
    float walkingSpeed;
    Waypoint *destinationWaypoint;
    BOOL active;
}
 
@property (nonatomic,assign) HelloWorldLayer *theGame;
@property (nonatomic,assign) CCSprite *mySprite;
 
+(id)nodeWithTheGame:(HelloWorldLayer*)_game;
-(id)initWithTheGame:(HelloWorldLayer *)_game;
-(void)doActivate;
-(void)getRemoved;
 
@end

Now, replace Enemy.m with the following code:

#import "Enemy.h"
#import "Tower.h"
#import "Waypoint.h"
 
#define HEALTH_BAR_WIDTH 20
#define HEALTH_BAR_ORIGIN -10
 
@implementation Enemy
 
@synthesize mySprite, theGame;
 
+(id)nodeWithTheGame:(HelloWorldLayer*)_game {
    return [[self alloc] initWithTheGame:_game];
}
 
-(id)initWithTheGame:(HelloWorldLayer *)_game {
	if ((self=[super init])) {
 
		theGame = _game;
        maxHp = 40;
        currentHp = maxHp;
 
        active = NO;
 
        walkingSpeed = 0.5;
 
        mySprite = [CCSprite spriteWithFile:@"enemy.png"];
		[self addChild:mySprite];
 
        Waypoint * waypoint = (Waypoint *)[theGame.waypoints 
                                           objectAtIndex:([theGame.waypoints count]-1)];
 
        destinationWaypoint = waypoint.nextWaypoint;
 
        CGPoint pos = waypoint.myPosition;
        myPosition = pos;
 
        [mySprite setPosition:pos];
 
        [theGame addChild:self];
 
        [self scheduleUpdate];
 
	}
 
	return self;
}
 
-(void)doActivate
{
    active = YES;
}
 
-(void)update:(ccTime)dt
{
    if(!active)return;
 
    if([theGame circle:myPosition withRadius:1 collisionWithCircle:destinationWaypoint.myPosition 
        collisionCircleRadius:1])
    {
        if(destinationWaypoint.nextWaypoint)
        {
            destinationWaypoint = destinationWaypoint.nextWaypoint;
        }else
        {
            //Reached the end of the road. Damage the player
            [theGame getHpDamage];
            [self getRemoved];
        }
    }
 
    CGPoint targetPoint = destinationWaypoint.myPosition;
    float movementSpeed = walkingSpeed;
 
    CGPoint normalized = ccpNormalize(ccp(targetPoint.x-myPosition.x,targetPoint.y-myPosition.y));
    mySprite.rotation = CC_RADIANS_TO_DEGREES(atan2(normalized.y,-normalized.x));
 
    myPosition = ccp(myPosition.x+normalized.x * movementSpeed,
                     myPosition.y+normalized.y * movementSpeed);
 
   [mySprite setPosition:myPosition];
 
 
}
 
-(void)getRemoved
{
    [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];
}
 
-(void)draw
{
    ccDrawSolidRect(ccp(myPosition.x+HEALTH_BAR_ORIGIN,
                        myPosition.y+16),
                    ccp(myPosition.x+HEALTH_BAR_ORIGIN+HEALTH_BAR_WIDTH,
                        myPosition.y+14),
                    ccc4f(1.0, 0, 0, 1.0));
 
    ccDrawSolidRect(ccp(myPosition.x+HEALTH_BAR_ORIGIN,
                        myPosition.y+16),
                    ccp(myPosition.x+HEALTH_BAR_ORIGIN + (float)(currentHp * HEALTH_BAR_WIDTH)/maxHp,
                        myPosition.y+14),
                    ccc4f(0, 1.0, 0, 1.0));
}
 
@end

That’s a fairly substantial code block – but it breaks down quite nicely. First, the enemy is initialized by passing a reference to the HelloWorldLayer object to it. Inside the init method, a few important variables are set:

  • maxHP: Defines how many hits this enemy can take. (Tough guy, eh?)
  • walkingSpeed: Defines how fast the enemy moves.
  • mySprite: Stores the visual representation of the enemy.
  • destinationWaypoint: Stores a reference to the next waypoint.

The update method is where the magic happens. This is called every frame, and it first checks to see if it’s reached the destination waypoint by using the collisionWithCircle method you wrote earlier. If so, it advances to the next waypoint – unless the enemy has reached the end, in which case the player is damaged.

Then, it moves the sprite along by moving in a straight line to the destination waypoint, according to the walking speed. It does this by the following algorithm:

  • Figure out a vector pointing from the current position to the target position, and then make it length of 1 so it’s easy to work with (the normalized variable).
  • Multiply the normalized vector by the movement speed to get an amount to move this frame. Add it to the current position to get the new position.

Finally, the draw method has a simple implementation of a health bar above the sprite. It first draws a red background, and then covers it with green according to the current HP of the enemy.

Now that the Enemy class is done, you can show them on the screen!

Switch to HelloWorldLayer.m and make the following changes:

//At the top of the file:
#import "Enemy.h"
 
//Add the following methods:
-(BOOL)loadWave {
    NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"Waves" ofType:@"plist"];
    NSArray * waveData = [NSArray arrayWithContentsOfFile:plistPath];
 
    if(wave >= [waveData count])
    {
        return NO;
    }
 
    NSArray * currentWaveData =[NSArray arrayWithArray:[waveData objectAtIndex:wave]];
 
    for(NSDictionary * enemyData in currentWaveData)
    {
        Enemy * enemy = [Enemy nodeWithTheGame:self];
        [enemies addObject:enemy];
        [enemy schedule:@selector(doActivate) 
               interval:[[enemyData objectForKey:@"spawnTime"]floatValue]];
    }
 
    wave++;
    [ui_wave_lbl setString:[NSString stringWithFormat:@"WAVE: %d",wave]];
 
    return YES;
 
}
 
-(void)enemyGotKilled {
    if ([enemies count]<=0) //If there are no more enemies.
    {
        if(![self loadWave])
        {
            NSLog(@"You win!");
            [[CCDirector sharedDirector] replaceScene:[CCTransitionSplitCols
                                                       transitionWithDuration:1
                                                       scene:[HelloWorldLayer scene]]];
        }
    }
}
 
// Add the following to the end of the init method:
// 5 - Add enemies
enemies = [[NSMutableArray alloc] init];
[self loadWave];
// 6 - Create wave label
ui_wave_lbl = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"WAVE: %d",wave]
                                             fntFile:@"font_red_14.fnt"];
[self addChild:ui_wave_lbl z:10];
[ui_wave_lbl setPosition:ccp(400,winSize.height-12)];
[ui_wave_lbl setAnchorPoint:ccp(0,0.5)];

All those code changes above deserve some explanation. The most important part is in the loadWave method; it reads the data from Waves.plist.

Take a look at the Waves.plist file and you will notice it contains three arrays. Each of these arrays represents a wave, which is simply a group of enemies that arrive together. The first array contains six dictionaries. Each of these dictionaries defines an enemy. In this tutorial, the dictionary stores only the time that the enemy should appear, but this dictionary could also be used to define the type of enemy or any other special property that differentiates your enemies.

The loadWave method checks for the next wave that should appear, creates the corresponding enemies based on the wave information, and schedules them to appear on screen accordingly.

The method enemyGotKilled checks the number of enemies present on screen, and if there are none, sends in the next wave. Later on, this same method will be used to determine if the player has won the game.

Compile and run the game now. Aha! The baddies are marching toward your precious base! (Betcha that old “All your base” meme popped into your head! Don’t feel too bad — it popped into our heads, too.)

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?

Where To Go From Here?

Here is a sample project with all of the code from the above Tower Defense Game tutorial.

If you want to keep playing around with this project, the sky’s the limit! There are so many things you can do to improve this game and make it a success. Here’s just a few ideas:

  • New enemy types with different speeds, health points, etc
  • New types of towers with unique attack patterns and costs
  • Multiple waypoint patterns for multiple enemy paths
  • Different levels with different tower base configurations

If you extend the game to add any cool new features or have any comments or questions, please join the forum discussion below!

Pablo Ruiz is a full-time mobile software developer and entrepreneur. He helps companies increase their profit by generating ideas, advising them and developing mobile apps and games for them through his company, InfinixSoft. Pablo is also the author of Cocos2d for iPhone: Beginner’s guide. To learn more, check out his LinkedIn profile. Pablo loves to talk about technology and games. Follow him on Twitter!

User Comments

20 Comments

[ 1 , 2 ]
  • For some reason i get an error saying use of undeclared identifier loadTowerPositions. What do i do?
    GameDvelop
  • For some reason i get an error saying "Use of unclared identifier loadTowerPositions" What do i do?
    GameDvelop
  • For some reason i get an error saying "use of unclared identifier loadTowerPositions" What to do?
    GameDvelop
  • I get an error saying use of undecalred identifier loadTowerPositions
    GameDvelop
  • For some reason i get an error saying "use of undeclared identifier loadTowerPositions what do i do?
    GameDvelop
[ 1 , 2 ]

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!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

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

  • Barbara Reichart
  • Jean-Pierre Distler
  • Matt Galloway

... 52 total!

Update Team

  • Zouhair Mahieddine

... 14 total!

Editorial Team

  • Alexis Gallagher

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!