Friday 29 October 2010

Tutorial 9 : Multiple Waves

In this tutorial we are going to create the class that will handle the creation of our waves, update our waves, and then move the player onto the next wave when the previous one has finished.

This will be one of the most important classes in our game. We are going to store all our waves in one big queue, and just move through them one by one with a short period of time in-between waves so the player has time to breath and make new towers. This class will also determine how many enemies we create per wave, so we can make the waves harder as time progresses.

So, let’s get started, start off by creating a new class called “WaveManager.cs” and add the following fields and properties :

private int numberOfWaves; // How many waves the game will have
private float timeSinceLastWave; // How long since the last wave ended
 
private Queue<Wave> waves = new Queue<Wave>(); // A queue of all our waves
 
private Texture2D enemyTexture; // The texture used to draw the enemies
 
private bool waveFinished = false; // Is the current wave over?
 
private Level level; // A reference to our level class
 
public Wave CurrentWave // Get the wave at the front of the queue
{
    get { return waves.Peek(); }
}

public List<Enemy> Enemies // Get a list of the current enemeies
{
    get { return CurrentWave.Enemies; }
}

public int Round // Returns the wave number
{
    get { return CurrentWave.RoundNumber + 1; }
}


As I explained earlier we are going to store our waves in a queue so we can access our waves in the order they were added. Next we are going to add in a constructor for the class :


public WaveManager(Level level, int numberOfWaves, Texture2D enemyTexture)
{
    this.numberOfWaves = numberOfWaves;
    this.enemyTexture = enemyTexture;

    this.level = level;
 
    for (int i = 0; i < numberOfWaves; i++)
    {
        int initialNumerOfEnemies = 6;
        int numberModifier = (i / 6) + 1;

        Wave wave = new Wave(i, initialNumerOfEnemies * 
            numberModifier, level, enemyTexture);
 
        waves.Enqueue(wave);
    }
}

This is where we are going to create all of the waves for the game. As you can see we progressively add wave after wave with the use of a for loop. The only interesting part of this method are the first two lines in the for loop.

These two lines alter the number of enemies each wave has, basically what these two lines do is every six waves, six extra enemies are produced. So for example the 1st, 2nd, 3rd, 4th and 5th waves will have 6 enemies, the 6th, 7th, 8th, 9th, 10th and 11th waves will have 12 enemies, and the 12th 13th… wave will have 18 enemies.

Of course the mechanism is extremely boring and in your own games you are probably going to want to add in boss levels, or fast levels etc, but for simplicity, this is all we are going to do.

Now we are going to add a method that will start the next wave :


private void StartNextWave()
{
    if (waves.Count > 0) // If there are still waves left
    {
        waves.Peek().Start(); // Start the next one

        timeSinceLastWave = 0; // Reset timer
        waveFinished = false;
    }
}

Here we just check if there is a wave to start, and if there is we start it, and reset our timer. We need to add a call to this method in our constructor otherwise our waves will never start! At the bottom of the constructor add this :


StartNextWave();

All that is left to do now is to update our waves and then draw them, let’s start by updating them :


public void Update(GameTime gameTime)
{
    CurrentWave.Update(gameTime); // Update the wave

    if (CurrentWave.RoundOver) // Check if it has finished
    {
        waveFinished = true;
    }
 
    if (waveFinished) // If it has finished
    {
        timeSinceLastWave += (float)gameTime.ElapsedGameTime.TotalSeconds; // Start the timer
    }

    if (timeSinceLastWave > 30.0f) // If 30 seconds has passed
    {
        waves.Dequeue(); // Remove the finished wave
        StartNextWave(); // Start the next wave
    }
}

As you can see there is nothing complicated happening here, we just update the wave we are on, and when it has finished, we wait 30 seconds, and then move onto the next wave. Simples!

Lastly, we just need to add a way to draw our current wave :


public void Draw(SpriteBatch spriteBatch)
{
    CurrentWave.Draw(spriteBatch);
}

And there we have it, an easy way of handling multiple waves! All that’s left to do now is to update “Game1.cs” to use our new WaveManager.

Go to “Game1.cs” and at the top of the class replace where we created our wave with this :


//Wave wave;
WaveManager waveManager;

Then in our LoadContent() method instead of initializing a single wave, initialize our wave manager :


//wave = new Wave(0, 10, level, enemyTexture);
//wave.Start();
 
waveManager = new WaveManager(level, 24, enemyTexture);

Update our WaveManager in the Update() method and make sure to pass in the list of enemies from our current wave to the player :


protected override void Update(GameTime gameTime)
{
    waveManager.Update(gameTime);
    player.Update(gameTime, waveManager.Enemies);
 
    base.Update(gameTime);
}


Finally in the draw method, draw our WaveManager instead of the single Wave :


//wave.Draw(spriteBatch);
waveManager.Draw(spriteBatch);

And we are finished, we now have multiple waves!!

Here is the source code for this tutorial.

36 comments:

  1. Awesome! Whats next?

    ReplyDelete
  2. Please continue this is a great set of examples. Thanks much!

    ReplyDelete
  3. amazing information for everybody looking forward to start a tower defense game.
    good job!

    ReplyDelete
  4. how long till the second wave comes? I only waited 1 minute but that seems too long

    ReplyDelete
  5. nevermind found it :)
    (timeSinceLastWave > 10.0f) // If 30 seconds has passed

    ReplyDelete
  6. Yea that's the line that needs to be changed :D

    ReplyDelete
  7. where are we supposed to set the number of waves? I want 100 waves. So where do I put that?

    ReplyDelete
  8. Hi Chicken,

    You would need to change this line in the Game1.cs LoadContent() method :

    waveManager = new WaveManager(level, 24, enemyTexture);

    to this :

    waveManager = new WaveManager(level, 100, enemyTexture);

    ReplyDelete
  9. ah, ok thanks. Also, how do you draw the towers so amazingly? They are awesome and I want to learn how to do that.
    And another thing too, if I want to make the bullet turn to face the enemy. My bullet is an arrow, not a ball and I want it to turn to the enemy when shot at it.
    Just as a note: I'm not following the tutorial exactly, because I am actually going to add some stuff and put it on the Xbox Live Indie Games Marketplace, because I don't want to infringe on copyright or anything. But still, this tutorial is helping a lot. I will be sure to give you credit and a free download code FireFly!

    ReplyDelete
  10. I am glad you like my towers!!! I just started by drawing a square body for the base of the tower, and then kept tweaking the shape / symmetry of it until I was satisfied with it. Then to finish them off I added a radial gradient colour!

    As for the rotating bullets, they should already work with the code I posted in Tutorial 7. As long as your arrow points up, it should get rotated to the right direction!

    It would be awesome to see some Tower Defences on the Xbox! It will be interesting to see what kind of control scheme you have created. I can't wait to see what you have come up with!

    ReplyDelete
  11. Ok thanks. 2 more things (sorry for so much asking XD)
    1) what photo editing program do you use?
    2) I tried the rotation thing, but it freezes as soon as it shoots.

    ReplyDelete
  12. It's ok. I use Paint.Net to do all my drawing / editing.

    What do you mean it freezes as soon as it shoots?

    If you mean the bullet stops rotating when it is shot, make sure you are calling this line :

    bullet.SetRotation(rotation);

    somewhere in your tower Update() code.

    ReplyDelete
  13. I mean the game freezes and it gives me some error. I will try that line of code and get back to you. thanks.

    ReplyDelete
  14. Ah ok, if you post the error I may be able to be more help.

    ReplyDelete
  15. edit:
    It works, i found out that the bullet was actually titled to the left, so it looked wrong. Thanks for your help though!

    ReplyDelete
  16. How would I go about making it so that you had to press a "ready" button before the next round started? I've almost got it now but when I press the button nothing happens. Its like the system is only checking if its ready instantly after the round is over, and after that never checking again.

    ReplyDelete
  17. How can I change it to send another wave every 20 seconds regardless of the first wave is still running?

    ReplyDelete
  18. Just change:

    if (CurrentWave.RoundOver) // Check if it has finished
    {
    waveFinished = true;
    }

    if (waveFinished) // If it has finished
    {
    timeSinceLastWave += (float)gameTime.ElapsedGameTime.TotalSeconds; // Start the timer
    }

    to:

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

    ReplyDelete
    Replies
    1. I already tried. So it probably will not be easy.

      Delete
  19. I'm not sure why that wouldn't work, what happened when you tried it? What went wrong?

    ReplyDelete
  20. That not working because we using queue. Queue get only first wave. When we want to start new wave. Last wave disappear.

    ReplyDelete
  21. So :) do you know how to do it ? :D

    ReplyDelete
  22. Ah ok, then you will need to switch the queue to a list, and store an int which is an index into that list that tells you which the current wave.

    Then in update you will probably need to replace:

    CurrentWave.Update(gameTime);

    with:

    for(int i = 0; i <= currentWaveIndex; i++)
    {
    waves[i].Update(gameTime);
    }

    ReplyDelete
    Replies
    1. I also decided to use list but do not know how. So thank you. :)

      Delete
  23. Also you would increment currentWaveIndex in the StartNextWave method and instead of doing:

    if (waves.Count > 0)

    do:

    if (currentWaveIndex < waves.Count)

    ReplyDelete
  24. So now i am stuck :D how do I do with currentWaveIndex

    ReplyDelete
  25. You would change:

    Queue waves...

    to:

    List waves = new List();
    int currentWaveIndex;

    Then:

    public Wave CurrentWave // Get the wave at the front of the queue
    {
    get { return waves.Peek(); }
    }

    to:

    public Wave CurrentWave // Get the wave at the front of the queue
    {
    get { return waves[currentWaveIndex]; }
    }

    Then:

    waves.Enqueue(wave);

    to

    waves.Add(wave)

    Then:

    if (waves.Count > 0) // If there are still waves left
    {
    waves.Peek().Start(); // Start the next one

    timeSinceLastWave = 0; // Reset timer
    waveFinished = false;
    }

    to:

    if (currentWaveIndex < waves.Count) // If there are still waves left
    {
    waves[currentWaveIndex].Start(); // Start the next one

    timeSinceLastWave = 0; // Reset timer
    waveFinished = false;
    }

    Then:

    if (timeSinceLastWave > 30.0f) // If 30 seconds has passed
    {
    waves.Dequeue(); // Remove the finished wave
    StartNextWave(); // Start the next wave
    }

    to:

    if (timeSinceLastWave > 30.0f) // If 30 seconds has passed
    {
    currentWaveIndex += 1;
    StartNextWave(); // Start the next wave
    }

    Then:

    CurrentWave.Update(gameTime);

    with:

    for(int i = 0; i <= currentWaveIndex; i++)
    {
    if (waves[i].WaveOver == false)
    waves[i].Update(gameTime);
    }

    And finally:

    CurrentWave.Draw(...

    with:

    for(int i = 0; i <= currentWaveIndex; i++)
    {
    if (waves[i].WaveOver == false)
    waves[i].Draw(...
    }

    ReplyDelete
  26. Yes :) this is what i want :) thank you very very much :)

    ReplyDelete
  27. Sorry but I need help again :( Now it is working and sending new wave on time but when it start new wave a tower stop shooting at last wave And shoot only on new. I think a bug is in CurrentWave. Tower shooting only on CurrentWave and that is changing when we change CurrentWaveIndex.

    ReplyDelete
  28. If I were you I would probably just scrap the current wave system and maybe roll your own.

    Instead of having the wave class store the enemies, maybe let the WaveManger store them and just let the Wave class add enemies to the master WaveManager.Enemies list.

    ReplyDelete
  29. Error 1 Inconsistent accessibility: property type 'tower_defence_1._0.Wave' is less accessible than property 'tower_defence_1._0.WaveManager.CurrentWave

    ReplyDelete
  30. public Wave CurrentWave // Get the wave at the front of the queue Inconsistent accessibility: property type 'tower_defence_1._0.Wave' is less accessible than property please help

    ReplyDelete
  31. Make sure they are both public classes:

    public class Wave

    public class WaveManager

    ReplyDelete
  32. Reviving an old thread here but just to let you know these tutorials are brilliant! thanks very much

    ReplyDelete
  33. They are really helpful, though I'm now having trouble trying to add different kinds of enemies since Firefly doesn't seem to cover that, I've finished this tutorial series.

    I was hoping that Wave or Wavemanager would make it simple to add new types of enemies but I just can't seem to figure it out.

    ReplyDelete