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 :

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 :

publicclass Bullet : Sprite

{

privateint damage;

privateint age;

privateint speed;

publicint Damage

{

get { return damage; }

}

publicbool 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 :

publicvoid 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 :

publicoverridevoid 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 :

publicvoid SetRotation(floatvalue)

{

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:

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.

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

protectedfloat 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 :

publicoverridevoid 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! :

publicbool 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 :

publicoverridevoid 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 :

publicoverridevoid 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.

19 comments:

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.

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.

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.

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).

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.

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...

HiNot 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 enemyHere is a pic that perhaps gives a better explanation: http://i.imgur.com/qKQ9y1o.png

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.