Sunday, 26 September 2010

Tutorial 5 : Towers

Now that we have something to shoot at, I think it is about time that we start adding in the “Tower” part of our Tower Defence. We will start off by creating a simple base class for the tower and getting it drawn, then we will move on to making it aim at our enemy. I will be leaving shooting, and placement of towers till next time. So, lets begin!

Start off by adding a new class to the project called “Tower.cs”, and make it inherit from the Sprite class :

public class Tower : Sprite
{
 
}

Next we will add in some of the basic fields and properties that all towers will share :


protected int cost; // How much will the tower cost to make
protected int damage; // The damage done to enemy's
 
protected float radius; // How far the tower can shoot
 
public int Cost
{
    get { return cost; }
}
public int Damage
{
    get { return damage; }
}

public float Radius
{
    get { return radius; }
}

All these fields are self explanatory. Next we will add in a simple constructor for our class :

public Tower(Texture2D texture, Vector2 position)
    : base(texture, position)
{

}


Notice how we don’t set any values for the tower’s fields, this is because each different tower type will have different costs and ranges and damages etc, so it will be easier if each type of tower sets it’s own values, as you will see later on in the tutorials.

And that’s the basics done, go back into Game1.cs and add the following field :

Tower tower; 

Before we can initialize the new tower we first need to add the following texture to the content project :

arrow tower

With that added we can move onto initializing and drawing our tower. In the LoadContent method add the following :


Texture2D towerTexture = Content.Load<Texture2D>("arrow tower");
tower = new Tower(towerTexture, Vector2.Zero);


All this code does is create a shiny new tower in the top left corner of our screen, using the texture we just added.

Now all that’s left is to draw it, so in the draw method just after where we draw our enemy, add the following :


tower.Draw(spriteBatch);

Now if you run the project you should see something like this :

image

Now we have our tower drawn, we can move on to something slightly more interesting, making the tower aim,

To start with we are going to give our a bigger range just for testing purposes, so go into Tower.cs and in the constructor add the following :


radius = 1000;


Next we will add in a simple helper method to check whether an enemy is in range of the tower :


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


Here we simple check whether the distance from the centre of the tower to the inputted position is within range. With that added we can move onto writing a method that will return the closest enemy to the tower, this will be how we choose what to shoot!

Before we add that we need one more field and property to store the enemy we are targeting. Add this to the top of Tower.cs :


protected Enemy target;

public Enemy Target
{
    get { return target; }
}

Now that's sorted, add the following method to the Tower class :


public void GetClosestEnemy(List<Enemy> enemies)
{
    target = null;
    float smallestRange = radius;
 
    foreach (Enemy enemy in enemies)
    {
        if (Vector2.Distance(center, enemy.Center) < smallestRange)
        {
            smallestRange = Vector2.Distance(center, enemy.Center);
            target = enemy;
        }
    }
}

This is where the magic happens, and it’s quite simple really, we loop through every enemy that is currently alive in the level, and if that enemy is closer than the previously found closest enemy, then the enemy is set to be the target.

Right, onto the aiming method, what we do here is find the direction from the tower to the target, and find out what that direction is by using some trigonometry.

For more information about finding an angle from a direction check out Riemers Tutorial.

Without for ado, here is the method :


protected void FaceTarget()
{
    Vector2 direction = center - target.Center;
    direction.Normalize();
 
    rotation = (float)Math.Atan2(-direction.X, direction.Y);
}


To call this method we simply override the Sprites Update method, and if we have a target, call FaceTarget() :


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

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

And that’s our tower class finished for now! Go back to Game1.cs and in the Update method, just after we Update the enemy, add the following :


if (tower.Target == null)
{
    List<Enemy> enemies = new List<Enemy>();
    enemies.Add(enemy1);
 
    tower.GetClosestEnemy(enemies);
}

tower.Update(gameTime);


Here, we just check if the tower has a target, and if it doesn’t we give it one using the method we added earlier.

And that’s it for now, if you run the project you should see your black dot following the path, with our shiny new tower following his every move!

Here is the source code for this tutorial.

18 comments:

  1. In the IsInRange method, should the if statement be:

    if (Vector2.Distance(center, position) <= radius)

    and not

    if (Vector2.Distance(center, position) <= range)

    ReplyDelete
  2. Yes it should, thanks for pointing that out!

    ReplyDelete
  3. My code and the source code download you give have the exact same everything. Gone over about 20 times, but for some reason, yours will Debug, mine won't because mine gets errors.

    ReplyDelete
  4. I can't really help without knowing which errors you are getting, if you post your errors here I will try and help.

    ReplyDelete
  5. Awesome tutorial! It's my 3th tut and yours is really easy to follow up till now, maybe you should point out to the people who can't get it to work to learn some basic C# and XNA.

    Just not sure about:
    public Tower(Texture2D texture, Vector2 position)
    : base(texture, position)

    Does it mean it gets it's texture and position from it's parent class?

    ReplyDelete
  6. Also, shouldn't there be an else in between those returns as now it returns true and false if it's within it's radius. If the distance isn't greater then the turrets radius? Not tested it though.

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

    return false;

    }

    ReplyDelete
  7. Hey Madmenyo, I'm glad you like my tutorials!

    This line :

    base(texture, position)

    is just calling the inherited sprite classes constructor, you might want to check this link out : http://www.dotnetperls.com/base

    An else statement isn't required there, this is because as soon a you call "return", the method gets exited so if the distance is less than the radius,

    return false; will never be reached. You may want to look at this : http://msdn.microsoft.com/en-us/library/1h3swy84(v=vs.71).aspx

    Let me know if that helps!

    ReplyDelete
  8. Well i'm still trying to understand inheritance to it's fullest but that link certainly helps. Thanks for the 2 links!

    I think i wasn't to far off with my statement. It loading it's base contructor and runs that first "to feed the texture and position" then it adds the radius to the tower object.

    In your full version at the bottem you didn't add the IsInRange method. It's not called yet (as yours works) but people might get into trouble if they use it and continue.

    ReplyDelete
  9. I have towers that I don't want to rotate. How would I modify this code to be able to shoot at the enemies without my tower turning to face them?

    ReplyDelete
  10. Hey, have you looked at this tutorial : http://xnatd.blogspot.com/2011/01/tutorial-12-adding-new-tower-type.html

    ReplyDelete
  11. I am moving houses for the first time (as a teen) i need tips and help for the move !?
    moving packers toronto

    ReplyDelete
  12. Looking for a house moving company? Any Suggestion...?
    local moving nyc

    ReplyDelete
  13. Really!!! I am very impressed after reading this blog. thanks for providing deep information for
    Moving companies In New York

    ReplyDelete
  14. Truly superb blog, I don’t have actual words to praise in regards for this
    movers michigan

    ReplyDelete
  15. I like your post & I will always be coming frequently to read more of your post. Thank you very much for your post once more.
    local moving companies houston tx

    ReplyDelete
  16. Another helpful post. This is a very nice blog that I will definitively come back to several more times this year!
    moving

    ReplyDelete
  17. This is easier and surely gives comfort to internet users. Thanks for sharing. Post like this offers great benefit. Thank you!
    low rates

    ReplyDelete