Friday 25 February 2011

Tutorial 14.5 : Drag and Drop Towers

As has been requested I have written another tutorial about the ability to “Drag and Drop” tower onto the level, at the end of the tutorial I will also discuss zooming and tower upgrades.




The first thing we will need to do is add a new event to the Button class; one which will fire when the player presses the button but doesn’t release the mouse. So go to Button.cs and just under the click event add the following event :

// Gets fired when the button is pressed.
public event EventHandler Clicked;
// Gets fired when the button is held down.
public event EventHandler OnPress;

Next go to the Update method and find the code that checks if the player is holding down the left mouse button, it will look something like this : 

// Check if the player holds down the button.
if (mouseState.LeftButton == ButtonState.Pressed &&
previousState.LeftButton == ButtonState.Released)
{
    if (isMouseOver == true)
    {
        // Update the button state.
        state = ButtonStatus.Pressed;
    }
}

Then just under where we set the state of the button add the following :

if (OnPress != null)
{
    // Fire the OnPress event.
    OnPress(this, EventArgs.Empty);
}

And that’s it for the button class, it really is that simple to add a new event to the class!

Now before we go to Game1.cs and subscribe to the new event, we must make a few additions to the player class, so go to Player.cs and add the following field and property :

// The index of the new towers texture.
private int newTowerIndex;

public int NewTowerIndex
{
    set { newTowerIndex = value; }
}

This new field will make it easier to choose which tower texture to draw when we are drawing a “preview” of the new tower.

Now we are going to add a new method that will draw a preview of the tower that the player wants to add :

public void DrawPreview(SpriteBatch spriteBatch)
{
    // Draw the tower preview.
    if (string.IsNullOrEmpty(newTowerType) == false)
    {
        int cellX = (int)(mouseState.X / 32); // Convert the position of the mouse
        int cellY = (int)(mouseState.Y / 32); // from array space to level space

        int tileX = cellX * 32; // Convert from array space to level space
        int tileY = cellY * 32; // Convert from array space to level space

     Texture2D previewTexture = towerTextures[newTowerIndex];
     spriteBatch.Draw(previewTexture, new Rectangle(tileX, tileY,
         previewTexture.Width, previewTexture.Height), Color.White);
}
}

First this method checks if the player is trying to create a new tower, and if they are, we draw a preview of tower in the cell that the mouse if hovering over.

Now there is just one more addition to be made to the player class, go to the AddTower method and just after the if statement that checks if there is a space for the tower and if the player can afford it, add the following :

else
{
    newTowerType = string.Empty;
}

What this will do is when the player tries to create a tower, but he can’t afford it or there isn’t space for it, the tower preview will disappear and the player will have to press the button again to make a new tower.

And that’s all that needs to be changed in the player class! So, let’s finally go back to Game1.cs and hook up our new to events!

Go to the LoadContent method and just after where we subscribed to the all of our buttons Clicked event, add the following :

arrowButton.OnPress += new EventHandler(arrowButton_OnPress);
spikeButton.OnPress += new EventHandler(spikeButton_OnPress);
slowButton.OnPress += new EventHandler(slowButton_OnPress);

Then find the following methods :

private void arrowButton_Clicked(object sender, EventArgs e)
{
    player.NewTowerType = "Arrow Tower";
}
private void spikeButton_Clicked(object sender, EventArgs e)
{
    player.NewTowerType = "Spike Tower";
}
private void slowButton_Clicked(object sender, EventArgs e)
{
    player.NewTowerType = "Slow Tower"
}

And replace them with these :

private void arrowButton_Clicked(object sender, EventArgs e)
{
    player.NewTowerType = "Arrow Tower";
    player.NewTowerIndex = 0;
}
private void spikeButton_Clicked(object sender, EventArgs e)
{
    player.NewTowerType = "Spike Tower";
    player.NewTowerIndex = 1;
}
private void slowButton_Clicked(object sender, EventArgs e)
{
    player.NewTowerType = "Slow Tower";
    player.NewTowerIndex = 2;
}

private void arrowButton_OnPress(object sender, EventArgs e)
{
    player.NewTowerType = "Arrow Tower";
    player.NewTowerIndex = 0;
}
private void spikeButton_OnPress(object sender, EventArgs e)
{
    player.NewTowerType = "Spike Tower";
    player.NewTowerIndex = 1;
}
private void slowButton_OnPress(object sender, EventArgs e)
{
    player.NewTowerType = "Slow Tower";
    player.NewTowerIndex = 2;
}

All that is left now is to draw the tower preview! So go to the draw method and just before we call spriteBatch.End() add the following :

player.DrawPreview(spriteBatch);

And that’s all there is to it! We now have the means to both “Drag and Drop” towers or just click and place them. If you only want to be able to Drag and Drop tower you will need to comment out the lines where we subscribe to our buttons Clicked event, and if you don’t want the player to Drag and Drop towers comment out where we subscribe to our buttons OnPress events.

Hopefully all of that made sense and was helpful to you all!

I have also been asked to write a tutorial about upgrading towers, however I think this is pretty simple and it would be good if you could do it on your own! But if you get stuck this is how I would do it:
  1. In the Tower class I would add a tier field that describes how upgraded the tower is.
  2. Then in each of the different tower classes I would add an Upgrade method that increases the tier of the tower and also increases the damage etc.
  3. In the Game1 class I would add a field for a new button and also another field that describes if it is hidden or visible.
  4. When a tower is selected I would make the button visible, and when no tower is selected I would make the button hidden.
  5. When the button is clicked I would call the selected towers Upgrade method if the player could afford the upgrade.
Also someone asked about zooming the camera in and out, for this I would look at one of the SpriteBatch.Begin() overloads that takes a matrix as a parameter. You could pass a scaling matrix (Matrix.CreateScale) to the Begin method to create a scaling effect, although this may cause some images to look stretched!

I hope this tutorial was useful to you all, thanks for reading!

Here is the source code for this tutorial.

27 comments:

  1. FireFly, again a great tutorial, please help in one problem: i try to make this: let say that our game have 10 waves of enemies, how could i change enemyTexture for wave 3 for example? I manage to change speed, health, bountygeven of those enemies but this enemyTexture give me real preoblem.
    Please forgive my bad English, and thank you in advance for your efforts.

    ReplyDelete
    Replies
    1. I believe that is one of the so much important information for me. And i am satisfied reading your article. However want to observation on few basic issues, The web site style is perfect, the articles is in reality nice.

      Delete
  2. yes, it will be great to see.How about other great features like menu, save, load, more levels to play...
    Cheers,
    L0ve b1tes

    ReplyDelete
  3. Come on, man. How to select a tower? And how i will know what type is that tower? This is a question i could not respond. Please help.

    Thanks a lot,
    Dragos

    ReplyDelete
  4. Hey, the first thing to do would be to add a Rectangle field and property to the Sprite class and initialize it so that it wraps around the sprite (see Button.cs on how to do this).

    The second thing you would need to do is add a bool field and property to the tower class which defines if the tower is selected.

    In the tower class I would define a Tower field and call it something like selectedTower.

    In the Player class in the Update method, inside of the if statement that checks if the player clicks the mouse, I would add an else statement that looks something like this :

    else
    {
    if (selectedTower != null)
    {
    if (!selectedTower.Bounds.Contains(mouseState.X, mouseState.Y))
    {
    selectedTower.Selected = false;
    selectedTower = null;
    }
    }

    foreach (Tower tower in towers)
    {
    if (tower == selectedTower)
    {
    continue;
    }

    if (tower.Bounds.Contains(mouseState.X, mouseState.Y))
    {
    selectedTower = tower;
    tower.Selected = true;
    }
    }
    }

    Let me know if you need some more help with this!

    ReplyDelete
  5. Hey Ryan,

    What I would suggest is passing a Texture2D[] to the WaveManager constructor with all the different enemy textures, and in the for loop, pass the new wave a different texture based on i e.g.

    if (i == 2)
    {
    Wave wave = new Wave(i, initialNumerOfEnemies *
    numberModifier, player, level, enemyTextures[index of enemy texture]);
    }

    Hope that helps and sorry for the delay!

    ReplyDelete
  6. """"""""""""""
    FireFly said...

    Hey, the first thing to do would be to add a Rectangle field and property to the Sprite class and initialize it so that it wraps around the sprite (see Button.cs on how to do this).

    The second thing you would need to do is add a bool field and property to the tower class which defines if the tower is selected.

    In the tower class I would define a Tower field and call it something like selectedTower.

    In the Player class in the Update method, inside of the if statement that checks if the player clicks the mouse, I would add an else statement that looks something like this :

    else
    {
    if (selectedTower != null)
    {
    if (!selectedTower.Bounds.Contains(mouseState.X, mouseState.Y))
    {
    selectedTower.Selected = false;
    selectedTower = null;
    }
    }

    foreach (Tower tower in towers)
    {
    if (tower == selectedTower)
    {
    continue;
    }

    if (tower.Bounds.Contains(mouseState.X, mouseState.Y))
    {
    selectedTower = tower;
    tower.Selected = true;
    }
    }
    }

    Let me know if you need some more help with this!

    """"""""""""""


    Hey FireFly,
    thanks for reply BUT i still get stuck, you see i want to select those towers for showing their radius of attack. I just don't really know how to do it. You said that in the Tower class should define a tower field (selectedTower) , but in the Player class that "selectedTower" is unrecognized without referencing from tower class (something like: in the Player class add field: Tower selectedTower;). I think i'm lost because i cannot see clear.
    Please make an example for showing radius of selected towers. A new small tutorial, please...
    Thanks again,
    Dragos

    ReplyDelete
  7. Sorry but I don't have much time at the moment! I suggest you re-read what I wrote!

    Sprite.cs : Add Rectangle field + property
    Tower.cs : Add bool field + property
    Player.cs : Add tower property

    To draw the selected towers radius, go into paint or something and draw a circle that has the same radius as the tower.

    Save that circle and add it to the content project.

    Load that image in, in the LoadContent method, and pass it to the Player class.

    When a new tower is added, pass the new tower the circle image.

    In the tower class draw the circle when the tower is selected!

    ReplyDelete
  8. thanks again, i'll try tomorrow. it should work because there, where at "Player.cs : Add tower property" i think was a mistake at first because it was missing.
    Thanks a lot, man. You are the man!
    Dragos

    ReplyDelete
  9. Hi again,
    i managed to get it work, towers could be selected AND it can be seen their radius. Thanks FireFly for your guiding, without your advices i'll still be in dark.
    My best wishes,
    Dragos.

    ReplyDelete
  10. Good job! I am glad you got it working!

    There is just one more thing I would like to add - instead of drawing a new circle image for all of the different tower radii, I would draw one big circle (about 160x160) and use that for all the towers but just scale it in the Tower.cs draw method by doing something like this :

    if (selected == true)
    {
    Vector2 radiusPosition = center - new Vector2(radius);

    Rectangle radiusRect = new Rectangle(
    (int)radiusPosition.X,
    (int)radiusPosition.Y,
    (int)radius * 2,
    (int)radius * 2);

    spriteBatch.Draw(rangeTexture, radiusRect, Color.White);
    }

    ReplyDelete
  11. this is very good idea, FireFly, but i want to keep every type of towers with different looking radius. It's more a matter of personal taste...
    Anyway your work on this tutorials is beyond my common words.
    Congrats,
    Dragos

    ReplyDelete
  12. FireFly great tutorial.

    I found one problem. The game crashes when we are out of waves. (To reproduce set the WaveManager to 2 or 3 waves)

    So we can´t win (-> game crash) or loose (-> counter goes to minus infinity)

    ReplyDelete
  13. Huh, well that's no good...

    I am in the process of writing a tutorial on how to integrate the game with the Game State Management sample from App Hub website. It will include adding victory and lose conditions so this shouldn't happen anymore!

    ReplyDelete
    Replies
    1. Hey Firefly, I tried to follow your exemple you gave to Dragos Andrei's when trying to do radius but I am still stuck to the whole properties in the player class.Do you think you could just give it a little look or check it out? Please respond! :(

      Delete
    2. Include in Player.cs:

      Tower selectedTower;

      For the SelectedTower.Bounds problem, I don't know.

      Delete
  14. Thank you so much FireFly!

    I have taught myself everything I know about C# (which isn't too much, but still). I have other working maze type games, but nothing like a TD.

    I am a quick learner, and now that I have a working version of yours I'm able to go back and study it step by step and create my own! This was exactly what I needed in order for me to actually learn something useful =P

    ~Chris

    ReplyDelete
  15. Hi,

    Don't know if you are interessted, but I've uploaded my version on http://doodledefense.janniklasrichter.de/dd_files/

    It runs also on WP7, uses the GameStateManagement from App Hub and supports upgrading Towers.

    ToDos are:
    -balancing (Game is very easy till now)
    -costs for upgrades (easy to implement, but first I want to do the balancing)
    -new Towers/Enemys/Levels

    ReplyDelete
  16. Oh, sry, in case you do not understand german, click on "Installieren" to download the setup (used XNA's buildin click-once installer). In-Game click on "Spielen" to start game and on "Beenden" to quit.

    ReplyDelete
  17. Heei Dragos.
    can you please give me a piece of your code, because I still get stuck.
    Attempts to upgrade the tower and view the radius.

    ReplyDelete
  18. Please can you write a tutorial for upgrading towers! I've been trying to do it for past few days and cant get it! :/

    ReplyDelete
  19. Thanks for your article. What I want to say is that while searching for a good on-line electronics store, look for a web page with entire information on critical factors such as the privacy statement, safety details, any payment procedures, as well as other terms in addition to policies. Always take time to look into the help as well as FAQ sections to get a superior idea of how the shop functions, what they are capable of doing for you, and how you can make use of the features.

    ReplyDelete
  20. Good day! This post couldn't be written any better! Reading this post reminds me of my old room mate! He always kept talking about this. I will forward this article to him. Fairly certain he will have a good read. Many thanks for sharing!

    ReplyDelete
  21. Hey FireFLy, thanks for the awesome tutorial. I wrote the code for tower defense and it worked great. I'm using this tutorial to make a small city builder for my college degree but i'm stuck on the building selection and don't quite understand what you wrote above to Dragos. i know where to put the code but i didn't understand how to put the fields and properties for references. If you could answer that it would be very helpfull.

    ReplyDelete
  22. Thank you for this excellent tutorial, ive learned alot by your explaining and showing how to do things. Thanks again for taking the amount of time it had to have taken to write all this.

    ReplyDelete
  23. can you put out the final source code? the codes from the zip file is not work at all, is just a basic with no runing enemy

    ReplyDelete
  24. Can I Please Have some help, whenever I drag my tower, only my slow towers come in, can I Please have some help?

    ReplyDelete