Sorry for the delay, I have had a very busy at the month! In this tutorial I will show you how to add in two type of cannon, the Spike tower. This tower will be able to shoot 8 bullets in eight different directions at a time.
This should be pretty straight forward seeing as we have designed the Tower class to be as extensible as possible, although we will have to make several adjustments to some of the other classes.
So let’s get started, the first thing we will do is add four new textures to our project :
The “spike tower” texture needs to be added to the main Content Project, whereas the other 3 textures need to be added to a new folder called “Spike Tower” that needs to be added in the GUI folder. Confused yet? When you have done this step you should end up with something like this :
Hopefully your still with me after that stage! Right, we can move onto creating a new class for our tower. Add a new class into the Tower folder and call it “SpikeTower.cs”. Next, make our new class inherit from the Tower class :
public class SpikeTower : Tower
// A list of directions that the tower can shoot in.
private Vector2[] directions = new Vector2[8];
// All the enimes that are in range of the tower.
private List<Enemy> targets = new List<Enemy>();
The first field will store of all the directions that the Spike Tower can shoot in (North, East, South… etc.). The next field will contain a list of all the enemies that are in range of the tower. You may be asking why not just have one target like the last tower? We need to store all these enemies because we aren’t just targeting one enemy, we are shooting bullets off in all directions and hoping one hits! We need to know all of the enemies that might be hit!
Next we need to give our tower a constructor :
/// <summary>
/// Constructs a new Spike Tower object.
/// </summary>
public SpikeTower(Texture2D texture, Texture2D bulletTexture, Vector2 position)
: base(texture, bulletTexture, position)
{
this.damage = 20; // Set the damage.
this.cost = 40; // Set the initial cost.
this.radius = 48; // Set the radius.
// Store a list of all the directions the tower can shoot.
directions = new Vector2[]
{
new Vector2(-1, -1), // North West
new Vector2( 0, -1), // North
new Vector2( 1, -1), // North East
new Vector2(-1, 0), // West
new Vector2( 1, 0), // East
new Vector2(-1, 1), // South West
new Vector2( 0, 1), // South
new Vector2( 1, 1), // South East
};
}
public Bullet(Texture2D texture, Vector2 position, Vector2 velocity, int speed, int damage)
: base(texture, position)
{
this.rotation = rotation;
this.damage = damage;
this.speed = speed;
this.velocity = velocity * speed;
}
public override void Update(GameTime gameTime)
{
}
We will build this method up step by step. The first thing we will add to this method is some code to create our bullets and make sure the travel in the right direction :
bulletTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;
// Decide if it is time to shoot.
if (bulletTimer >= 1.0f && targets.Count != 0)
{
// For every direction the tower can shoot,
for (int i = 0; i < directions.Length; i++)
{
// create a new bullet that moves in that direction.
Bullet bullet = new Bullet(bulletTexture, Vector2.Subtract(center,
new Vector2(bulletTexture.Width / 2)), directions[i], 6, damage);
bulletList.Add(bullet);
}
bulletTimer = 0;
}
// Loop through all the bullets.
for (int i = 0; i < bulletList.Count; i++)
{
Bullet bullet = bulletList[i];
bullet.Update(gameTime);
// Kill the bullet when it is out of range.
if (!IsInRange(bullet.Center))
{
bullet.Kill();
}
// Loop through all the possible targets
for (int t = 0; t < targets.Count; t++)
{
// If this bullet hits a target and is in range,
if (targets[t] != null && Vector2.Distance(bullet.Center, targets[t].Center) < 12)
{
// hurt the enemy.
targets[t].CurrentHealth -= bullet.Damage;
bullet.Kill();
// This bullet can't kill anyone else.
break;
}
}
// Remove the bullet if it is dead.
if (bullet.IsDead())
{
bulletList.Remove(bullet);
i--;
}
}
Our Spike Tower is nearly finished however there is one more property that needs to be added, however first we need to go back to “Tower.cs” and add the following property :
public virtual bool HasTarget
{
// Check if the tower has a target.
get { return target != null; }
}
I will explain the significance of this property later. While we are still in “Tower.cs” there is one more change that needs to be made, find the GetClosestEnemy method and make it a virtual method, so change this :
public void GetClosestEnemy(List<Enemy> enemies)
to this :
public virtual void GetClosestEnemy(List<Enemy> enemies)
Now then, go back to “SpikeTower.cs” and add the following property :
public override bool HasTarget
{
// The tower will never have just one target.
get { return false; }
}
Again I will explain this in more detail later. Before we can move onto to adding a button for this tower, there is a few more changes to be made (yes I know I said we only had one more property to add…). We need to override the GetClosestEnemy method, this is because our now Spike Tower handles enemies differently than the normal method, so let’s override it and add in some new logic :
public override void GetClosestEnemy(List<Enemy> enemies)
{
// Do a fresh search for targets.
targets.Clear();
// Loop over all the enemies.
foreach (Enemy enemy in enemies)
{
// Check wether this enemy is in shooting distance.
if (IsInRange(enemy.Center))
{
// Make it a target.
targets.Add(enemy);
}
}
}
As you can probably see, instead of trying to find the closest enemy, we are just finding all of the enemies that are in range of the tower (see above for the explanation why).
Before we can add a new button for this tower, we need to make a small method to the Players Update method, so go ahead and find the Update method in “Player.cs” and more specifically this line :
if (tower.Target == null)
// Make sure the tower has no targets.
if (tower.HasTarget == false)
// The textures used to draw our tower.
private Texture2D[] towerTextures;
It is no longer possible to only store one texture to use for all of the Towers, we will needs a separate texture for each tower. We need to modify the constructor to accommodate this change :
/// <summary>
/// Construct a new player.
/// </summary>
public Player(Level level, Texture2D[] towerTextures, Texture2D bulletTexture)
{
this.level = level;
this.towerTextures = towerTextures;
this.bulletTexture = bulletTexture;
}
Now that we have access to different textures to use for different types of tower, we can go ahead and find the AddTower method and change it so it can handle the SpikeTower. You need to change the switch statement so it looks like this :
switch (newTowerType)
{
case "Arrow Tower":
{
towerToAdd = new ArrowTower(towerTextures[0],
bulletTexture, new Vector2(tileX, tileY));
break;
}
case "Spike Tower":
{
towerToAdd = new SpikeTower(towerTextures[1],
bulletTexture, new Vector2(tileX, tileY));
break;
}
}
Button spikeButton;
Next, find the LoadContent method. We need to adjust the code that initializes the player so that we pass in a texture array instead of a single texture :
Texture2D[] towerTextures = new Texture2D[]
{
Content.Load<Texture2D>("arrow tower"),
Content.Load<Texture2D>("spike tower")
};
player = new Player(level, towerTextures, bulletTexture);
Now go down to where we load in the textures for the arrow tower and just under, load in the textures for the spike tower :
// The "Normal" texture for the spike button.
Texture2D spikeNormal = Content.Load<Texture2D>("GUI\\Spike Tower\\spike button");
// The "MouseOver" texture for the spike button.
Texture2D spikeHover = Content.Load<Texture2D>("GUI\\Spike Tower\\spike hover");
// The "Pressed" texture for the spike button.
Texture2D spikePressed = Content.Load<Texture2D>("GUI\\Spike Tower\\spike pressed");
And just under that initialize our new button and assign a click event to it :
// Initialize the spike button.
spikeButton = new Button(spikeNormal, spikeHover,
spikePressed, new Vector2(32, level.Height * 32));
spikeButton.Clicked += new EventHandler(spikeButton_Clicked);
private void spikeButton_Clicked(object sender, EventArgs e)
{
player.NewTowerType = "Spike Tower";
}
//Update the spike button.
spikeButton.Update(gameTime);
spikeButton.Draw(spriteBatch);
What about the level editor ?
ReplyDeleteWould be very nice !
BTW, thanks for the excellent tutorial series, it really kicks ass man !
Instead of using 3 different images, wouldn't be easier when drawing (Draw method) use Color.Green, Color.Red, for example ?
ReplyDeleteThanks
lol now the other tower is not shooting?? oO
ReplyDeletethats not profitable xD
plz fix that mistake =)
i think that lines are wrong..
ReplyDeleteif (tower.Target == null)
and replace it with the following :
// Make sure the tower has no targets.
if (tower.HasTarget == false)
Because the arrow towers will never shoot, if this code is addet
Hey Dwan, yes it probably would :) However what if in your final game you decided that when the button is pressed it should do something other then change colour?
ReplyDeleteThis way you have more control over the buttons appearance, but if you don't need this extra control, simply do what you suggested above :)
Hey Leonarado, thanks for the kind words!
ReplyDeleteI think that a level editor is probably unnecessary for a Tower Defence because of how small the levels are, however if you really want to make one you could check out Nick Gravelyn's tutorials.
You are right Anonymous the HasTarget property should look like this :
ReplyDeletepublic virtual bool HasTarget
{
// Check if the tower has a target.
get { return target != null; }
}
Thanks for pointing out the bug! :)
A custom tower class would be nice !
ReplyDeleteHey FireFly no problem =)
ReplyDeleteI had to say your tutorials are very nice!
And yes an custom tower class would be really nice =)
Hi cool tutorial. I really like it :-)
ReplyDelete- A Button to upgrade the tower would be nice. So he does more damage or has a longer shooting range.
- The player sould get some money when he kills an enemy or survives a round.
- Multiplayer would be nice too, but i think its nothing easy to do.
Best Regards
Alex
Your finding target code is very inefficient.
ReplyDeleteYour bullet should be the one keeping track of when it should expire, not the tower.
So you think for every bullet that is fired, the bullet should store some kind of reference to the tower that fired it so that the bullet knows when it is out of the range of the tower? Should the bullet also be aware of all the enemy's as well so it knows when it hits one...?
ReplyDeleteI think my method of just letting the tower handle its own bullets would be more efficient than that.
Of course there are better ways to handle the bullets more efficiently, i.e using some kind of resource pool.
Maybe I misunderstood you?
It doesn't need a reference to the tower or to the enemies, you can simply just say once it has travelled x distance, it has expired.
ReplyDeleteBut the tower will still have to remove the dead bullet from it's list of bullets, and for your idea to work each bullet would need to keep track of the towers range, I just don't see the benefit of the bullet keeping track of when it should expire.
ReplyDeleteAfter adding this new tower... everything works fine except now I can place towers on top of each other.
ReplyDeletenevermind, the problem was my code not yours. my bad.
ReplyDeleteI get this error:
ReplyDeleteAn unhandled exception of type 'System.NullReferenceException' occurred in TowerTest.exe
Additional information: Object reference not set to an instance of an object.
for this line of code in the ArrowTower class:
if (Vector2.Distance(bullet.Center, target.Center) < 12)
I'm commented here^^^ because the error only started after I made the spike tower
ReplyDeleteHey Anonymous,
ReplyDeleteAre you getting this error at the end of the tutorial or are you still working through it?
If you haven't finished the tutorial yet the chances are that it may get fixed later in the tutorial!
If not just leave another comment and I will try and help some more!
Sorry for taking so long to reply I was away on vacation. I have worked through the tutorial and didn't have a problem till I tested the game and added a few arrow towers. Also I was wondering, if you aren't too busy, could you write a tutorial about making a tower the does splash damage or help point me in the right direction of how I should go about doing it? I've tried figuring it out by myself but I've only ended up screwing other things up lol
ReplyDeletesorry I meant when I added a few spike towers, not arrow towers.
ReplyDeleteI'm sure you're busy but I was wondering if I could get some help with what I posted above? Thanks.
ReplyDeleteI think you are getting the error because the code you posted above should like like this :
ReplyDeleteif (target != null && Vector2.Distance(bullet.Center, target.Center) < 12)
Let me know if this helps!
Hi firefly, im having this problem which whenever i place my spike tower on the map it show null reference from the line if (IsCellClear() == true && towerToAdd.Cost <= money) i have gone through and through with this tutorial and the source code and have absolutely no idea what the problem im facing, would you kindly help me? =D
ReplyDelete