Sunday, 3 October 2010

Tutorial 7 : Firepower

Now that we have the ability to place down tower’s and have a basic targeting system working, we can move onto making our tower’s shoot so we can finally obliterate that little black dot smugly walking along our path!

We are going to start off by creating our first type of tower, the “Arrow Tower”, then move on to creating a “Bullet” class, and to top it all off, we will combine the two classes and create a targeting shooting tower.

Right, let’s begin! Before we create our new type of tower, there are a couple of changes we need to make to “Tower.cs”. Add the following field to the Tower class :

protected Texture2D bulletTexture;

Then modify the constructor so it looks like this :


public Tower(Texture2D texture, Texture2D bulletTexture, Vector2 position)
    : base(texture, position)
{
    this.bulletTexture = bulletTexture;
}


You will notice the new constructor takes in another texture, this will be the texture that is used when drawing all the towers bullets.

With those small changes done, add a new class to the project called “ArrowTower.cs” and modify it so it looks like the following :


public class ArrowTower : Tower
{
    public ArrowTower(Texture2D texture, Texture2D bulletTexture, Vector2 position)
        : base(texture, bulletTexture, position)
    {
        this.damage = 15; // Set the damage
        this.cost = 15;   // Set the initial cost

        this.radius = 80; // Set the radius
    }
}


All we are doing here is making our new class inherit from the Tower class, and setting up the tower’s settings in the tower’s constructor. That is it for our ArrowTower class for now, we will come back to it later to add in an Update method where our bullets will be created. But before we can create a bullet, we need to create a Bullet class. So let’s do that now, add a new class called “Bullet.cs” and modify it so it looks like this :


public class Bullet : Sprite
{
    private int damage;
    private int age;
 
    private int speed;
 
    public int Damage
    {
        get { return damage; }
    }

    public bool IsDead()
    {
        return age > 100;
    }
 
    public Bullet(Texture2D texture, Vector2 position, float rotation, 
        int speed, int damage) : base(texture, position)
    {
        this.rotation = rotation;
        this.damage = damage;
 
        this.speed = speed;
    }
}

This is the basic shell for our bullet class, all the fields and properties should be pretty obvious except maybe for age. We will use the age field to track when a bullet should die. Next we are going to add in a simple method to “kill” the bullet :


public void Kill()
{
    this.age = 200;
}

I’m sure you are asking, how will this kill a bullet? Well, if you look up at the last bit of code we added in the “IsDead” property, you will see that if a bullet is older than 100, it is classed as dead, so by setting a bullet’s age to 200, we are essentially killing it.
One of the final things we are going to add is an update method for the bullet, without this, the bullets would just sit in the barrel of the tower doing nothing :


public override void Update(GameTime gameTime)
{
    age++;
    position += velocity;
 
    base.Update(gameTime);
}

It’s pretty simple what we are doing here, first we increase the age of the bullet, if we didn’t do this and by some bug the bullet didn’t hit an enemy, it could live forever! Next we are add the velocity of the bullet onto it’s position, and I know what your going to say, that ‘Wait, we haven’t set a velocity yet!’ and that’s what we are going to do next.

In the real world, when we shoot a bullet out of a gun, it is pretty much going to move linearly (in a straight line) until it hits something, but we aren't making a simulation! It would be frustration for the player if his towers never hit any enemies because they moved to fast and the bullets just zoomed behind the enemies! So we are going to cheat, we are going to “bend” our bullets, as if we were in the film “Wanted”. To do this, every frame we are going to making sure our bullet is moving towards our target :


public void SetRotation(float value)
{
    rotation = value;

    velocity = Vector2.Transform(new Vector2(0, -speed), 
        Matrix.CreateRotationZ(rotation));
}


I’m not sure if you have come across the Transform method before but it is incredibly useful, it allows us to manipulate a vector by using a matrix. In our case we want to rotate a vector representing our speed, to have the same rotation as our tower, like the following example where the dotted arrow represents our initial velocity before rotation:

diag
We are using the Matrix.CreateRotationZ method because we want to rotate the velocity around the axis that sticks out of the screen. One thing to notice is we use -speed. This is because when the tower hasn't been rotated it points up, which in terms of vectors is (0, -1). Right, that’s our bullet class finished.

For more information on this see Riemers Tutorial.

We are now going to make a few more changes to Tower.cs, at the top of Tower add the following fields :


protected float bulletTimer; // How long ago was a bullet fired
protected List<Bullet> bulletList = new List<Bullet>();

The bulletTimer field will track the time since the last bullet fired, we can use this to work out when the next bullet should be fired. The second field will just store all our bullets in a handy list.

Now we are going to have to modify the update method so it looks like this :


public override void Update(GameTime gameTime)
{
    base.Update(gameTime);

    bulletTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;

    if (target != null)
    {
        FaceTarget();

        if (!IsInRange(target.Center))
        {
            target = null;
            bulletTimer = 0;
        }
    }
}


Let’s go through the changes step by step.


bulletTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;


This line just updates the timer, this will be set to zero when a bullet is fired.


if (!IsInRange(target.Center))

{
    target = null;
    bulletTimer = 0;
}


Here we check whether or not our target is in range of the tower, and if it’s not, we set the target to null, there’s no point on wasting ammo on enemies we will never hit! We also reset the bullet timer so that we start the bullet shooting cycle again when a new target is picked.

Once again it seems like I have overshot myself and called a method that doesn’t even exist, lets fix that now! :


public bool IsInRange(Vector2 position)
{
    return Vector2.Distance(center, position) <= radius;
}

IsInRange is a very simple method that just checks whether or not a point is within the range of the tower. Last but not least we will add in the method that will draw our bullets :


public override void Draw(SpriteBatch spriteBatch)
{
    foreach (Bullet bullet in bulletList)
        bullet.Draw(spriteBatch);
 
    base.Draw(spriteBatch);
}


We loop through all the bullets and draw them before we draw our actual tower, this stop’s the bullets being drawn on top of the tower that shot them. And that’s it for now for our tower class!

We are now going to go back to the ArrowTower class to finish it off by adding an Update method :


public override void Update(GameTime gameTime)
{
    base.Update(gameTime);


The first thing we are going to do in this method is check whether or not enough time has passed to fire a bullet, and whether or not we actually have something to shoot at. If both checks come back true, then we will create a new bullet at the center of the tower, and then reset the timer.


if (bulletTimer >= 0.75f && target != null)
{
    Bullet bullet = new Bullet(bulletTexture, Vector2.Subtract(center,
        new Vector2(bulletTexture.Width / 2)), rotation, 6, damage);
 
    bulletList.Add(bullet);
    bulletTimer = 0;
}

After that we will loop through all of the bullets and update and “bend” them towards the target. We will also check if the bullet has gone out of range of the tower, and if it has, we will kill it! The last check we will make is whether the bullet is still alive, and if it isn’t, we will remove it from the game :


for (int i = 0; i < bulletList.Count; i++)
{
    Bullet bullet = bulletList[i];

    bullet.SetRotation(rotation);
    bullet.Update(gameTime);
 
    if (!IsInRange(bullet.Center))
        bullet.Kill();

    if (bullet.IsDead())
    {
        bulletList.Remove(bullet);
        i--;
    }
}


And that’s it for our towers for now, we should now have a fully functioning tower class, but before it will shoot, we must first update a couple of things.

Add the following field to Player.cs :


private Texture2D bulletTexture;


Then change the constructor to this :


public Player(Level level, Texture2D towerTexture, Texture2D bulletTexture)
{
    this.level = level;

    this.towerTexture = towerTexture;
    this.bulletTexture = bulletTexture;
}


Lastly find the following line in the Update method :


Tower tower = new Tower(towerTexture, new Vector2(tileX, tileY));

And replace it with this :


ArrowTower tower = new ArrowTower(towerTexture, 
    bulletTexture, new Vector2(tileX, tileY));


All that is left now is to go into Game1.cs and find the code where we create a new Player, and replace it with this :


Texture2D towerTexture = Content.Load<Texture2D>("arrow tower");
Texture2D bulletTexture = Content.Load<Texture2D>("bullet");
 
player = new Player(level, towerTexture, bulletTexture);

Then all we must do is add the following image to the Content Project :

bullet That’s it for this tutorial, we now have working shooting towers!!!

Here is the source code for this tutorial.

19 comments:

  1. Thanks thanks thanks. This is made it very easy to implement my own version of TowerDefence. Please keep posting :)

    ReplyDelete
  2. Shouldn't you be reusing the bullet instances? It seems like the longer the game lasts, the more memory you are going to absorb creating bullet instances. Perhaps you could have a predefined number of bullets for each tower and if a new bullet needs to be fired, revive the first dead one and use it. Just a suggestion to cut down on memory use.

    ReplyDelete
    Replies
    1. No because once a bullet is used, it is removed from the list, and GC removes the instance.

      Delete
  3. Yes I probably should, but that goes a bit beyond the scope of this tutorial.

    The most efficient way of handling bullets would probably be to use some sort of Resource Pool like you suggested.

    If anyone is interested there is a great article on this here : http://swampthingtom.blogspot.com/2007/06/generic-pool-collection-class.html

    ReplyDelete
  4. Should the enemies be dieing at this point yet?

    ReplyDelete
  5. Yes they should be, but make sure you check out the bug fixes I posted in the next tutorial!

    ReplyDelete
  6. How exactly would we make it so the tower shoots faster or slower? -Thanks in Advance, Chicken21200

    ReplyDelete
  7. You will need to find this line :

    if (bulletTimer >= 0.75f && target != null)

    in your custom tower class and change 0.75f to whatever number you want - a smaller number means a faster firing tower, a bigger number means a slower firing tower!

    Hope that helps!

    ReplyDelete
  8. I can add tower and pretty much everything is working except for the bullets. I have them loaded in my project and etc. But there is no bullet firing and the enemy isn't dying. I looked into your files and there isnt a Draw() method in Bulet.cs. And I can't lauch it (your project, mine does lauch). Thanks for your help.

    ReplyDelete
  9. Nevermind I fixed it, the draw was meant to be in the ArrowTower class.

    ReplyDelete
  10. I don't know if you're still reviewing this blog, hope so! :)

    I'm still really enjoying it, but I've run into a conundrum.

    The way you've set up the towers here, your player has a "BulletTexture" and a "TowerTexture", which means that each tower the player creates will look the same and fire the same bullet.

    Is it possible (or mostly just feasible) to have a ContentManager in the tower or bullet class to load their own textures? That way, each type of tower has it's own texture, and you can (down the road) add different looking bullets.

    Thanks!

    ReplyDelete
  11. I'm still here to answer comments! :)

    At the moment you are right that all the towers look the same, however in tutorial 12 you will start adding in different types of tower so this will be explained more.

    But basically instead of storing just one bullet texture and one tower texture I store an array of different tower textures in the player class and pick the appropriate texture based on which tower is being placed.

    Hopefully this will become more clear in later tutorials (It is the same with enemies as well).

    ReplyDelete
  12. can these codes be use for tower defense in win7 mobile?

    ReplyDelete
  13. I keep getting the error:
    "inconsistent accessibility field type 'system.collections.generic.list..." for the bulletlist. The class is public and the list is protected like it's supposed to be. I'm not sure what's wrong.

    ReplyDelete
  14. One thing to note here, in a previous tutorial you said to add the following to Tower.cs:

    public bool IsInRange(Vector2 position)
    {
    if (Vector2.Distance(center, position) <= radius)
    return true;

    return false;
    }

    But in this tutorial you're saying that we haven't implemented it yet and we should do it like this:

    public bool IsInRange(Vector2 position)
    {
    return Vector2.Distance(center, position) <= radius;
    }

    ReplyDelete
  15. Yes this can be adapted to the phone. You just have to go in and modify the player class where it reads the mousestate, and handle your touch there however you want. Already have it running on the phone just for fun...

    ReplyDelete
  16. Hi
    Not sure if I am to late to the party :)
    I'm having this problem where my bullet, most of the time passes behind the enemy, at the corner of the enemy
    Here is a pic that perhaps gives a better explanation: http://i.imgur.com/qKQ9y1o.png

    ReplyDelete
  17. Hmm that is odd, does increasing the bullet speed help?

    ReplyDelete
  18. Well it does work somewhat at really high speed but then the update wont catch all collides.. But I think its a problem due to the long range, it works perfect at close range like your game, but we are doing it in full screen mode, having a sniperTower with infinte range, they will miss 90% of the bullets unless the enemies are in a straight line with the tower.





    ReplyDelete