Tuesday, 5 October 2010

Tutorial 8 : Waves (Featuring Tower Modifications)

In this tutorial we will be focusing on creating so called “waves” of units.

We will start off by fixing some nasty bugs that I overlooked in the last two tutorials.

Then we will move onto creating our Wave class. This will contain information such as how many enemies should be created this wave, and which of these created enemies are still alive. It will be responsible for creating, updating and drawing the enemies.

For the sake of simplicity I am not going to add in any “boss levels” or “fast levels” etc. for now as that would mean coming up with some pretty difficult formulas!So, let’s begin, to start with go into “ArrowTower.cs” and find where we update our bullets. We are going to add in some code that will actually make our bullets damage the tower’s target. Just before we check if the bullet is dead add the following :

All this code does is check whether the bullet has hit out target, and if it has, make sure we take some health off of the enemy and then kill the bullet. So where did the 12 come from – quite simply it is this :

(width of bullet / 2) + (width of enemy / 2) = (12 / 2) + (12 / 2)

So now the code to update our bullets should look like this :

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 (Vector2.Distance(bullet.Center, target.Center) < 12)

{

target.CurrentHealth -= bullet.Damage;

bullet.Kill();

}

if (bullet.IsDead())

{

bulletList.Remove(bullet);

i--;

}

}

Next you need to go to Tower.cs and, in the Update method, find the lines that look like this :

if (target != null)

{

FaceTarget();

if (!IsInRange(target.Center))

{

target = null;

bulletTimer = 0;

}

}

and replace them with these :

if (target != null)

{

FaceTarget();

if (!IsInRange(target.Center) || target.IsDead)

{

target = null;

bulletTimer = 0;

}

}

I’m sorry I missed these things out in the last tutorial!! I would have just modified the last tutorials to fix these bugs but I thought it might confuse some people.

And that’s our modifications to the tower class finished. If you run the project now and place a tower, it should shoot the enemy and the enemy should gradually change colour when hit.

Right, let’s move onto our waves, create a new class called “Wave.cs” and add the following fields and variables :

privateint numOfEnemies; // Number of enemies to spawn

privateint waveNumber; // What wave is this?

privatefloat spawnTimer = 0; // When should we spawn an enemy

privateint enemiesSpawned = 0; // How mant enemies have spawned

privatebool enemyAtEnd; // Has an enemy reached the end of the path?

privatebool spawningEnemies; // Are we still spawing enemies?

private Level level; // A reference of the level

private Texture2D enemyTexture; // A texture for the enemies

public List<Enemy> enemies = new List<Enemy>(); // List of enemies

publicbool RoundOver

{

get

{

return enemies.Count == 0 && enemiesSpawned == numOfEnemies;

}

}

publicint RoundNumber

{

get { return waveNumber; }

}

publicbool EnemyAtEnd

{

get { return enemyAtEnd; }

set { enemyAtEnd = value; }

}

public List<Enemy> Enemies

{

get { return enemies;}

}

A lot of these variables should explain themselves, however it may not be clear why we have added a couple of them. The reason we have a reference to the Level class is so that we can access the level’s waypoints. We must also store a copy of the enemy texture so that can be passed to the enemy when we create it.

Next we will add a constructor for the class :

public Wave(int waveNumber, int numOfEnemies,

Level level, Texture2D enemyTexture)

{

this.waveNumber = waveNumber;

this.numOfEnemies = numOfEnemies;

this.level = level;

this.enemyTexture = enemyTexture;

}

I’m not going to insult you be explaining this :P. Now we are going to add to add three new methods, one to update the wave, and one to start the wave and one to add a new enemy. Here is the first :

privatevoid AddEnemy()

{

Enemy enemy = new Enemy(enemyTexture,

level.Waypoints.Peek(), 50, 1, 0.5f);

enemy.SetWaypoints(level.Waypoints);

enemies.Add(enemy);

spawnTimer = 0;

enemiesSpawned++;

}

This is most of the magic of the wave class will happen. This is the method were you can alter the stats of the enemy based on the wave number. e.g Say that you want wave 5 to be a fast stage, you would do something like this :

if (waveNumber == 5)

{

float speed = 2.0f;

enemy = new Enemy(enemyTexture,

level.Waypoints.Peek(), 50, 1, speed);

enemy.SetWaypoints(level.Waypoints);

}

The second method we will add looks like this :

publicvoid Start()

{

spawningEnemies = true;

}

This method just tells the wave that it is time to start churning out enemies for us to gun down. Before we can move onto the Update method, go to Enemy.cs and replace the IsDead property with this :

publicbool IsDead

{

get { return !alive; }

}

We needed to make this change because it is no longer enough to make sure that an enemy has health, we also need to make sure is not at the end of the path.

Now we can move onto the Update method :

publicvoid Update(GameTime gameTime)

{

}

The first thing we will add to this method is the code to spawn our enemies :

if (enemiesSpawned == numOfEnemies)

spawningEnemies = false; // We have spawned enough enemies

if (spawningEnemies)

{

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

if (spawnTimer > 2)

AddEnemy(); // Time to add a new enemey

}

First we check if we still need to spawn enemies. Then if we do, we update the time since an enemy was last created. If 2 seconds have passed since the last time we created an enemy, then we will spawn a new enemy.Next we will add some code to update and remove dead enemies :

for (int i = 0; i < enemies.Count; i++)

{

Enemy enemy = enemies[i];

enemy.Update(gameTime);

if (enemy.IsDead)

{

if (enemy.CurrentHealth > 0) // Enemy is at the end

{

enemyAtEnd = true;

}

enemies.Remove(enemy);

i--;

}

}

Here, after we Update the enemy, we check if he is “dead”. I put “dead” in speech marks because, although IsDead tells us if an enemy should be dead, it can also indicate that the enemy has reached the end of his path. This is what we check for next, if an enemy IsDead but still has health this must mean he is at the end of his path, so we therefore set enemyAtEnd to true seeing an enemy has reached the end.All that is left to do is draw the enemies in this wave :

publicvoid Draw(SpriteBatch spriteBatch)

{

foreach (Enemy enemy in enemies)

enemy.Draw(spriteBatch);

}

And there we have it, our wave class!! Now let’s make a few changes to Game1.cs so our changes will show up. At the top of Game1.cs replace the line were we define our enemy with the following :

//Enemy enemy1;

Wave wave;

Next find where we initialize our enemy and replace it with this :

//enemy1 = new Enemy(enemyTexture, Vector2.Zero, 100, 10, 0.5f);

//enemy1.SetWaypoints(level.Waypoints);

wave = new Wave(0, 10, level, enemyTexture);

wave.Start();

After that find where we update our enemy and replace it with this :

//enemy1.Update(gameTime);

//List<Enemy> enemies = new List<Enemy>();

//enemies.Add(enemy1);

wave.Update(gameTime);

player.Update(gameTime, wave.Enemies);

Finally find where we draw the enemy and replace it with this :

//enemy.Draw(spriteBatch);

wave.Draw(spriteBatch);

Now you should see about ten black dots appear at the start of your path and march to the end!! Happy hunting.

Made a quick change to the Tower.CS that was bugging me. Towers were facing dead enemies after they 'disappeared'. (Note, I'm just starting this tutorial, so I'm not sure if this will conflict with the WAVES code at all.)

if (target != null) //Target?{ if (!target.IsDead) //Is it Alive? FaceTarget(); //Face it

I also experienced the whole "towers targetting dead mobs" problem and while Somber's fix does fix the visual problem, the mob is still following the waypoint. I fixed that by opening up Enemy.cs and in the update changing:

I have one question.. i want to create more different types of enemies (example: enemy1 and enemy2). Enemy1 will have speed 0.5f, enemy2 will have only 50 health...I think that I've tried everything, but I've always failed. All help will be very usefull.Thanx!

Thax for quick response, but i think that we don't think same. Well.. I would like to have two different types of enemys in one wave. Something like this:Enemy enemy = new Enemy(enemyTexture, level.Waypoints.Peek(), 50, 1, 0.5f);Enemy enemyTWO= new Enemy(enemyTextureTWO, level.Waypoints.Peek(), 30, 1, 2.5);

I hope that this is more understandable.And by the way, great work ;)Thanx

Oh ok I understand. Then what I would do is store a new list of enemies in the Wave class and call it something like enemiesToSpawn. In the wave constructor fill this list with all of the different enemies you would like to spawn:

Hello FireFly, FIrst of all,thank's for your tutorial,its a good starting point to work on,now i have a question,what's about animating the enemy's?i mean, i have tryed to use instead of using a single image,would be nice to make the enemie's moving,IE like an animated car and so on,i have splitted your Sprite class away from the enemy class,and worked a bit over it, right now i can import and make the sprite work,but the problem comes with the enemy spawn, the first enemy its ok,it's anymated and everything, but as soon as the second enemy spawns,the first one disappear,under debugging i can see the enemy count, and its ok, but on the screen, i can see only one enemy,do you have any hint?thank's again for your time,and sorry for the long post :)

everything works great except for in Wave.cs in the Draw methodenemy.Draw(spriteBatch)has an error: No overload for method 'Draw' takes 1 argumentsits inheriting the Sprite.Draw method and wants a spriteBatch and a color. im not sure if i did something wrong or if there was something left out.any help would be great!