App Hub
Banner of Game Development Tutorial

shooting things

Adding bullets the player can shoot at enemies

 

While we’ve come a long way in making a moving player and enemies, the only way we currently have for the player to interact with enemies is to ram into them, doing damage to the player as well as the enemies. That’s not a strategy that’ll last long. Let’s give the player a weapon they can use.

The simplest kind of weapon is just an automatically-firing stream of projectiles that go from left to right, starting from the player’s position. In many ways, these are like the Enemy class, but going the opposite direction and having some different logic. And like the Enemy, these projectiles should be in their own class. Let’s create a new Projectile class.

Add a new class to your project by pressing SHIFT + ALT + C, and typing in the name Projectile.cs. Press ENTER.

inside projectile.cs

Inside our new class, let’s make a quick modification to the top section. Delete all the using statements at the top, and replace them with the following lines:

// Projectile.cs
//Using declarations
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

Now, as we did in our other classes, we’ll set up all the data we’ll need and stub in our standard Initialize(), Update() and Draw() methods to fill in later.

// Image representing the Projectile
public Texture2D Texture;

// Position of the Projectile relative to the upper left side of the screen
public Vector2 Position;

// State of the Projectile
public bool Active;

// The amount of damage the projectile can inflict to an enemy
public int Damage;

// Represents the viewable boundary of the game
Viewport viewport;

// Get the width of the projectile ship
public int Width
{
get { return Texture.Width; }
}

// Get the height of the projectile ship
public int Height
{
get { return Texture.Height; }
}

// Determines how fast the projectile moves
float projectileMoveSpeed;


public void Initialize()
{
}
public void Update()
{
}
public void Draw()
{
}

As you can see, the data and methods are very similar to the Enemy class. Note these projectiles don’t have an Animation associated with them, they’re just a static texture. They’ll be moving so fast an animation would hardly be worth it. However, with a few changes you could easily animate them if you wanted to.

This data needs to be filled in, so let’s make our Initialize() method to take care of that. Replace the Initialize() method you just stubbed in with this version:

public void Initialize(Viewport viewport, Texture2D texture, Vector2 position)
{
Texture = texture;
Position = position;
this.viewport = viewport;

Active = true;

Damage = 2;

projectileMoveSpeed = 20f;
}

We’ve set up our initial values – time to dive into the Update() method, where we’ll decide how far to move the projectiles and when they should be considered “dead”.

Replace the Update() method you just stubbed in with this version:

public void Update()
{
// Projectiles always move to the right
Position.X += projectileMoveSpeed;

// Deactivate the bullet if it goes out of screen
if (Position.X + Texture.Width / 2 > viewport.Width)
Active = false;
}

Finally, we need a Draw() method to ensure these projectiles are drawing on the screen just like the other game objects. Replace the Draw() method you just stubbed in with this version:

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, Position, null, Color.White, 0f,
new
Vector2(Width / 2, Height / 2), 1f, SpriteEffects.None, 0f);
}

Projectile.cs is now completely set up. Much of it is as simple as (or even simpler than) the Enemy class. And, like the Enemy class, we’ll be spawning many of them very quickly. That work, we’ll need to do in the Game1 class, with a List and some timing variables. Let’s switch over to Game1.

Switch to the Game1 class by double-clicking the Game1.cs file in the Solution Explorer in Visual Studio.

inside game1.cs

We’re back inside our familiar Game1 class, and we’re going to take a few familiar steps, starting with adding our List and timing variables up at the top of the class.

Look for the first { mark under the start of the Game1 class, and go just below Random random; that you added in the previous step. Add a new line and then add the following:

Texture2D projectileTexture;
List<Projectile> projectiles;

// The rate of fire of the player laser
TimeSpan fireTime;
TimeSpan previousFireTime;

Those are all the variables we need – let’s make sure they’re initialized. Look down the code, find the Initialize() method. Inside that method, add these lines:

projectiles = new List<Projectile>();

// Set the laser to fire every quarter second
fireTime = TimeSpan.FromSeconds(.15f);

Since the projectiles have a graphic associated with them, we’ll need to load that from disk in LoadContent(). Look down the code, find the LoadContent() method. Inside that method, below the enemyTexture = call, add these lines:

projectileTexture = Content.Load<Texture2D>("laser");

That’s the standard stuff taken care of. Now we’ll add our own custom methods, starting with an AddProjectile() method, similar to the AddEnemy() method we made a few steps ago. This method, similar to its Enemy counterpart, will create a new projectile and add it to the list we’ve created.

Find some empty space inside Game1 class, preferably near the Update() method, make a new line, and you’re ready to go. Add the following lines to make your new AddProjectile() method:

private void AddProjectile(Vector2 position)
{
Projectile projectile = new Projectile();
projectile.Initialize(GraphicsDevice.Viewport, projectileTexture,position);
projectiles.Add(projectile);
}

Now, we need to call AddProjectile() from somewhere. Since the Player is the one that’s shooting the projectiles, it makes sense to put the call inside UpdatePlayer().

Look for the UpdatePlayer() method inside the Game1 class, and add the following lines at the end:

// Fire only every interval we set as the fireTime
if (gameTime.TotalGameTime - previousFireTime > fireTime)
{
// Reset our current time
previousFireTime = gameTime.TotalGameTime;

// Add the projectile, but add it to the front and center of the player
AddProjectile(player.Position + new Vector2(player.Width / 2, 0));
}

We’re adding projectiles, but we need some update code to handle the ones that are already in the air. We’ve written code like this before, it won’t be hard to put together an UpdateProjectiles() method. Find some free space inside the Game1 class outside of any other method, and let’s get to it.

Add the following lines to make an UpdateProjectiles() method:

private void UpdateProjectiles()
{
// Update the Projectiles
for (int i = projectiles.Count - 1; i >= 0; i--)
{
projectiles[i].Update();

if (projectiles[i].Active == false)
{
projectiles.RemoveAt(i);
}
}
}

We have to call UpdateProjectiles(), of course – a good place for this is inside the Update() method. Look for the Update() method inside the Game1 class, and after the UpdateCollision() call, add the following lines:

// Update the projectiles
UpdateProjectiles();

Perfect. We’re adding projectiles, and we’re updating them; we just need to draw them. Add the following code to the Draw() method, after the call to enemies.Draw():

// Draw the Projectiles
for (int i = 0; i < projectiles.Count; i++)
{
projectiles[i].Draw(spriteBatch);
}

What’s missing? They’ll spawn, move, draw – but they won’t collide with anything yet. We need to add code to the UpdateCollision() method that takes these projectiles into account. We’ll want to walk through the projectile list and for every projectile, do a nested loop inside that loop, where we walk through the entire enemy list and look for collisions. This gets to be a lot of calculations. As your game gets bigger you’ll need to think of ways to reduce the number of calculations, but for now, checking them all against one another will be fine.

Look for the UpdateCollision() method inside the Game1 class, and at the bottom of the method, add the following lines:

// Projectile vs Enemy Collision
for (int i = 0; i < projectiles.Count; i++)
{
for (int j = 0; j < enemies.Count; j++)
{
// Create the rectangles we need to determine if we collided with each other
rectangle1 = new Rectangle((int)projectiles[i].Position.X -
projectiles[i].Width / 2,(int)projectiles[i].Position.Y -
projectiles[i].Height / 2,projectiles[i].Width, projectiles[i].Height);

rectangle2 = new Rectangle((int)enemies[j].Position.X - enemies[j].Width / 2,
(int)enemies[j].Position.Y - enemies[j].Height / 2,
enemies[j].Width, enemies[j].Height);

// Determine if the two objects collided with each other
if (rectangle1.Intersects(rectangle2))
{
enemies[j].Health -= projectiles[i].Damage;
projectiles[i].Active = false;
}
}
}

That’s it! We’ve got projectiles, and they’re deadly against enemies. Let’s have a look.

running the game

Build and run your game by pressing CTRL+F5.

It should be very obvious if you’ve gotten it right – you should see projectiles aplenty when you play the game, similar to the screen below.

Image of Shooter Running with Projectiles Firing from the Player Ship

When the projectiles hit enemies, the enemies should absorb a few shots and then disappear. If it’s not happening, don’t worry – there’s a code checkpoint just below where you can catch up with the latest code.

Next, we’re going to make the combat a little more explosive, by adding animated explosion effects.

checkpoint! check your work and get code

We've gone from simple graphics flying around to full-on ship combat with collision and shooting projectiles!

We recommend you take a second to download a fresh copy of the project with the source code synchronized to the end of this step, especially if you're having trouble getting your code to run.

Just close your Visual Studio environment, click on the link below for the platform you're developing on, then unzip the file and open the enclosed .SLN with Visual Studio. You'll be all caught up!

Shooting Things - Windows Shooting Things - for Xbox 360 Shooting Things - for Phone

Move on to the next step: Making Things Explode

var gDomain='m.webtrends.com'; var gDcsId='dcschd84w10000w4lw9hcqmsz_8n3x'; var gTrackEvents=1; var gFpc='WT_FPC'; /*<\/scr"+"ipt>");} /*]]>*/
DCSIMG