Let’s make TriangleShooter a better game. This time around, we’ll be putting the shooter in TriangleShooter, by adding in the ability for the player to shoot. We’ll be starting with last week’s code, so you can go over and grab that from my SkyDrive share.

I tried a few different ways of making the player shoot in my development of the game, and the best one I have found so far is to have you always laying out a steady stream of shots. Towards the end of the series, I’ll be doing some play mechanics tweaking, and we can investigate some of the other ways, but that’s how I’m going to do things in this article.

If you’re going to shoot, you need a bullet. You can get my highly stylized, square bullet from the SkyDrive share. To begin with, download that, then add it into the TriangleShooterContent project by dragging it and dropping it in there. We’ll also need a class to handle the bullet, which is going to look familiar. Create a new class in the TriangleShooter project named Bullet.cs, and set the content to the following:

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  4. namespace TriangleShooter
  5. {
  6.     class Bullet
  7.     {
  8.         public Texture2D Avatar { get; set; }
  9.         public Vector2 Position { get; set; }
  10.         public float Rotation { get; set; }
  11.         public float Speed { get; set; }
  12.     }
  13. }

We’ll set up the variables at the top of the game class to hold the texture, the list of Bullets, and to monitor the passing time to shoot, along with a constant to tweak the timer a bit.

Variables for Bullets
  1. const int ShootDelay = 250;
  2. TimeSpan timeToShoot;
  4. List<Bullet> bullets;
  5. Texture2D txBullet;

In the Initialize method, we set up the list for the bullets, and set the time to shoot from the constant value we set up.

  1. bullets = new List<Bullet>();
  3. timeToShoot = new TimeSpan(0, 0, 0, 0, ShootDelay);

And add some code into the LoadContent method to load in the texture.

  1. txBullet = Content.Load<Texture2D>("Bullet");

And now we’ve got everything we need. We only have the Update and Draw to go. The Update is more interesting, so let’s start there.

  1. timeToShoot -= gameTime.ElapsedGameTime;
  3. if (timeToShoot <= TimeSpan.Zero)
  4. {
  5.     timeToShoot = new TimeSpan(0, 0, 0, 0, ShootDelay);
  6.     bullets.Add(new Bullet() { Avatar = txBullet, Position = new Vector2(player.Position.X + player.Avatar.Width * (float)Math.Cos(player.Rotation), player.Position.Y + player.Avatar.Height * (float)Math.Sin(player.Rotation)), Rotation = player.Rotation, Speed = 15f });
  8. }
  10. foreach (Bullet b in bullets.ToList())
  11. {
  12.     b.Position = new Vector2(b.Position.X + b.Speed * (float)Math.Cos(b.Rotation), b.Position.Y + b.Speed * (float)Math.Sin(b.Rotation));
  14.     if (!graphics.GraphicsDevice.Viewport.Bounds.Contains(new Point((int)b.Position.X, (int)b.Position.Y)))
  15.     {
  16.         bullets.Remove(b);
  17.     }
  18.     else
  19.     {
  20.         foreach (Enemy enemy in enemies.ToList())
  21.         {
  22.             if (new Rectangle((int)enemy.Position.X - enemy.Avatar.Width / 2, (int)enemy.Position.Y - enemy.Avatar.Height / 2, enemy.Avatar.Width, enemy.Avatar.Height).Contains((int)b.Position.X, (int)b.Position.Y))
  23.             {
  24.                 bullets.Remove(b);
  25.                 enemies.Remove(enemy);
  26.                 break;
  27.             }
  28.         }
  29.     }
  30. }

The first part of the code is similar to what we do to spawn enemies. We subtract the elapsed time from the remaining time, and if the time is less than zero, we spawn a new bullet, setting it’s position to the front of the player and the rotation to the players rotation. We then update all existing bullets. Because we may need to remove them from the master list, we make a copy in the foreach by using the .ToList() function. We move them in the direction based on their rotation, and see if they have left the bounds of the screen. If they have, we remove them from the update list. To optimize things here, we should do something like create a list of decommissioned bullets, and reuse them rather than creating new ones each time, but we won’t do that this time around. If they are within the bounds of the screen, we check collision with each of the enemies, again using the .ToList() function because we might need to remove items. If they collide, we remove both the bullet, and the enemy that collided with it.

This is all we need to do, but we do need to draw the bullets. Luckily, this is a simple process, just add a few lines into Draw.

  1. foreach (Bullet b in bullets)
  2. {
  3.     spriteBatch.Draw(b.Avatar, b.Position, null, Color.White, b.Rotation, new Vector2(b.Avatar.Width / 2, b.Avatar.Height / 2), 1f, SpriteEffects.None, 0f);
  4. }

And now we have shooting!


Next week, we’ll do some refactoring to make the code easier to work with, and see if we can add in some death behavior.

Download the latest version of the source code.