ScriptHighlighter

Monday, December 15, 2014

Windows 8 Game Development using C#, XNA and MonoGame 3.0: Building a Shooter Game Walkthrough – Part 7: Load Your Weapons!

In this continuation of Tara Walker's series: Windows 8 Game Development Using C#, XNA and MonoGame we will expand our game by giving the player the ability to fire deadly laser blasts at the enemy. We will create the laser game object, add code to fire the laser when the user presses the space bar, and detect collisions between our laser blasts and the enemy areal mines.  You can find a link to her original series here.


Creating the Laser Blast

Step 1 - Create the Laser Class

Add a new class to your project called Laser.cs. Add the following using statements to the top of the class definition.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

As in our player and enemy classes, we will stub out the methods for Draw(), Update(), and Initialize().  We will also define a couple of new variables that represent the characteristics of our laser blast.  Add the following definitions to the top of the Laser.cs class.

// animation the represents the laser animation.
public Animation LaserAnimation;

// the speed the laser travels
float laserMoveSpeed = 30f;

// position of the laser
public Vector2 Position;

// The damage the laser deals.
int Damage = 10;

// set the laser to active
public bool Active;

// Laser beams range.
int Range;

// the width of the laser image.
public int Width
{
    get { return LaserAnimation.FrameWidth; }
}

// the height of the laser image.
public int Height
{
    get { return LaserAnimation.FrameHeight; }
}

Design Break:

As you look over the variables above, hopefully you are already getting cool ideas for power ups and enhancements.  Perhaps the player can get bonuses to the laser's damage and speed characteristics.  Will you permit your laser to fly all the way across the screen or will it fizzle out shortly after being fired?  As a game designer these are all aspects you must consider.  All of which will have a effect on game play and ultimately, the finished product. It's a good idea to write these ideas down as part of a game design before you begin coding.  Having a formal game design will save you time during implementation.  Now back to coding!

Add the following implementations for the Initialize(), Update(), and Draw() methods.

public void Initialize(Animation animation, Vector2 position)
{
    LaserAnimation = animation;
    Position = position;
    Active = true;
}

public void Update(GameTime gameTime)
{
    Position.X += laserMoveSpeed;
    LaserAnimation.Position = Position;
    LaserAnimation.Update(gameTime);
}

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

Step 2 - Add the Laser Texture Content and Load It

Locate the laser.xnb from the downloaded folder and add it to your project.  In my project I placed this texture file in the \Content\Graphics folder.  Once you have added this file. Right-click on the file in the solution explorer and click "properties".  Set the "build action" setting  to "content" and set the "copy to output directory" setting to "Copy if newer".  

Next let's add the following variable near the top of the game class Game1.cs.  This variable will hold the texture for our laser beam.



// texture to hold the laser.
Texture2D laserTexture;
// govern how fast our laser can fire.
TimeSpan laserSpawnTime;
TimeSpan previousLaserSpawnTime;
I will mention the two TimeSpan variables in detail later.  For now let's just add them just below the declaration of our texture variable.  Add the following line of code the the LoadContent() method of the Game class Game1.cs.  
// load th texture to serve as the laser
laserTexture = Content.Load<Texture2D>("Graphics\\laser");

Step 3 - Initialize our Laser Object List


Design Break:

Before we can add our laser we must make a couple of game design decisions.  Firstly, will the player be able to fire only one laser beam at a time or will we permit them to fire multiple beams in a hail of flaming hot destruction?  Trust me, it matters!  For this tutorial we will go with the latter option. We will establish a variable that governs a rate of fire for our laser cannon.  That means we may have more than one active beam at a time.  This requires us to create a collection of laser beam objects.

To store the volley of laser fire, add this list definition near the top of the Game1 class just below the laserTexture declaration:

List<Laser> laserBeams;
Next add the following variable definitions to the Initialize() method.  I will explain each one below.

// init our laser
laserBeams = new List<Laser>();
const float SECONDS_IN_MINUTE = 60f;
const float RATE_OF_FIRE = 200f ;
laserSpawnTime = TimeSpan.FromSeconds(SECONDS_IN_MINUTE /RATE_OF_FIRE);
previousLaserSpawnTime = TimeSpan.Zero;

The first variable should be fairly obvious. We are simply instantiating ("newing up") a strongly typed list of our Laser class.  The next line is for nothing more than readability.  I create a float to hold the number of seconds in one minute.  Then I create another float to hold a rate of fire (lasers per minute).  II use the seconds in one minute and the rate of fire variables to calculate a time span, in seconds, that governs how quickly my laser can fire another round.  In this case I want a rate of fire of 200 rounds per second so my time span between shots will be less than one second (60/200).  The very last line initializes a variable that will be set when a new laser is fired.  When the time between "now" and the previousLaserSpawnTime is greater or = the laserSpawnTime then I can fire a new beam.


Step 4 - Add Code to Fire a Laser

To support firing the player's weapons I added the following two classes to the Game1.cs class.

protected void FireLaser(GameTime gameTime)
{
    // govern the rate of fire for our lasers
    if (gameTime.TotalGameTime - previousLaserSpawnTime > laserSpawnTime)
    {
        previousLaserSpawnTime = gameTime.TotalGameTime;
        // Add the laer to our list.
        AddLaser();
    }
}

protected void AddLaser()
{
    Animation laserAnimation = new Animation();
    // initlize the laser animation
    laserAnimation.Initialize(laserTexture,
        player.Position,
        46,
        16,
        1, 
        30,
        Color.White,
        1f,
        true);

    Laser laser = new Laser();
    // Get the starting postion of the laser.

    var laserPostion = player.Position;
    // Adjust the position slightly to match the muzzle of the cannon.
    laserPostion.Y += 37;
    laserPostion.X += 70;
    
    // init the laser
    laser.Initialize(laserAnimation, laserPostion);
    laserBeams.Add(laser);
    /* todo: add code to create a laser. */
    // laserSoundInstance.Play();
}


The FireLaser() method is pretty straight-forward.  First, I check to see if enough time has passed between now and the last time I fired a laser.  If it has then I record the current time and call the AddLaser() method.

The AddLaser() method simply initializes a new instance of the animation class using our laser beam texture.  Then we set the laser's starting position to that of the player's current position.  We assume the laser is emitted from the player's ship.  Finally, we pass our animation class into the Initialize() method of our laser class and add it to our collection of Laser objects.  Did you see the commented code to play the sound of a laser blast?  That's a teaser for a future tutorial! 

The last thing we need to do is to wire up our FireLaser() method to the press of the space bar on the keyboard or the X button on the XBox controller.  Add this code to the UpdatePlayer() method of the Game1.cs class.


if (_currentKeyboardState.IsKeyDown(Keys.Space) || _currentGamePadState.Buttons.X == ButtonState.Pressed)
{
    FireLaser(gameTime);
}

Step 5 - Add Code to Track Laser Beams and Detect Collisions
Now that we can add laser beams to our collection we need to loop through all active lasers and update their positions and detect collisions with enemies.  This part is pretty simple.  Just add the following code to the Update() method of the Game1.cs class.

// update laserbeams
for (var i = 0; i < laserBeams.Count;i++ )
{
    laserBeams[i].Update(gameTime);
    // Remove the beam when its deactivated or is at the end of the screen.
    if (!laserBeams[i].Active || laserBeams[i].Position.X > GraphicsDevice.Viewport.Width)
    {
        laserBeams.Remove(laserBeams[i]);
    }
}

Notice that I never used the "range" variable I declared earlier in my code.  I just decided that a beam would fly across the screen until it reached the right side or collided with an enemy mine. 

Next we need to loop through all enemies and check to see if they are colliding with any of the laser beams in our collection.  To do this, we need a nested loop.  Add this code to the UpdateCollisions() method in the Game1.cs class.


Rectangle laserRectangle;


This rectangle is used to define the bounding box around a laser beam and is used in collision detection against the rectangles of enemies.  Locate the section of code in the UpdateCollisions() method where we loop through our collection of enemies testing for collisions with the player.  Add this code INSIDE the enemy loop as shown.

// detect collisions between the player and all enemies.
enemies.ForEach(e =>
{
   //create a retangle for the enemy
   enemyRectangle = new Rectangle(
       (int)e.Position.X,
       (int)e.Position.Y,
       e.Width,
       e.Height);

   // now see if this enemy collide with any laser shots
   laserBeams.ForEach(lb =>
   {
       // create a rectangle for this laserbeam
       laserRectangle = new Rectangle(
       (int)lb.Position.X,
       (int)lb.Position.Y,
       lb.Width,
       lb.Height);

       // test the bounds of the laer and enemy
       if (laserRectangle.Intersects(enemyRectangle))
       {
           // play the sound of explosion.
           var explosion = explosionSound.CreateInstance();
           explosion.Play();
           
           // Show the explosion where the enemy was...
           AddExplosion(e.Position);

           // kill off the enemy
           e.Health = 0;

           //record the kill
           myGame.Stage.EnemiesKilled++;

           // kill off the laserbeam
           lb.Active = false;

           // record your score
           myGame.Score += e.Value;
        }
    });
});

Step 6 - Draw the Laser Beams

The last step is to add code in the Draw() method of the Game1.cs class to loop through each object in the laser beam collection and invoke it's draw method.  Add this code:


// Draw the lasers.
foreach (var l in laserBeams)
{
    l.Draw(spriteBatch);
}
Well That is it! If you have done everything correctly then your player's ship will now be armed with a deadly laser cannon.  Experiment with the rate of fire and laser speed variables.  Find a combination that is both fun and challenging.  

Now you can blast away at enemies!


Next time we will make these pesky enemy mines EXPLODE!

You can find the source code for this step here:




2 comments:

  1. I may be a complete noob but, why do the graphics look bigger than in the screenshot when I compile?

    ReplyDelete