Windows 8 Game Development using C#, XNA and MonoGame 3.0: Building a Shooter Game Walkthrough – Part 6: Creating Enemies and Detecting Collisions

Overview

Last time we met in Part 5 of this blog series, we created an animation to bring the ship to life by simulation of movement and propulsion. We also created and added a more realistic background to the game by the creation of a parallaxing background by layering three distinct images and moving them in and/or of the game screen’s view by simulate the sky with clouds moving as the ship flies through the background. In the comments of Part 5, we got some great feedback on the variations of how people handled the Parallaxing background screen refresh on various machines and would not have a smooth jitter. One such comment and example was well throughout and I want to share it with you all so that you can update your game accordingly if desired. Tim Eicher ( teicher) updated the Shooter parallaxing background by creating a function WrapTextureToLeft and WrapTextureToRight (shown below) and changing the position and scale variables (see full code at https://bitbucket.org/teicher/shootertutorial. Thanks Tim for sharing this Excellent code update with us all!

private void WrapTextureToLeft(int index)

{

// If the textures are scrolling to the left, when the tile wraps, it should be put at the

// one pixel to the right of the tile before it.

int prevTexture = index – 1;

if (prevTexture < 0)

prevTexture = _positions.Length – 1;

_positions[index].X = _positions[prevTexture].X + _texture.Width;

}

private void WrapTextureToRight(int index)

{

// If the textures are scrolling to the right, when the tile wraps, it should be

//placed to the left of the tile that comes after it.

int nextTexture = index + 1;

if (nextTexture == _positions.Length)

nextTexture = 0;

_positions[index].X = _positions[nextTexture].X – _texture.Width;

}

Now on with the tutorial. Let’s just into how to create enemies for the game and animate the enemy graphic.

Creating Enemy Graphics and Animation

The enemies that will be created will be an Animation that will move from right to left based upon their position setting. In order to make the game challenging, we will have to created several enemy objects at a time and manage a list of those object created. In addition, we will have each enemy object created appear at random vertical positions when they appear in the game.

Step 1 – Create Enemy class

Add a new class to your project and name the class Enemy.cs. Delete any existing using statements and replace them with the following using statements below.

using System;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Graphics;

Now let’s set up all the data and the function stubs for the Enemy class with the Initialize(), Update() and Draw() methods we have similarly used in our other classes by adding the following lines:

// Animation representing the enemy

public Animation EnemyAnimation;

// The position of the enemy ship relative to the top left corner of the screen

public Vector2 Position;

// The state of the Enemy Ship

public bool Active;

// The hit points of the enemy, if this goes to zero the enemy dies

public int Health;

// The amount of damage the enemy inflicts on the player ship

public int Damage;

// The amount of score the enemy will give to the player

public int Value;

// Get the width of the enemy ship

public int Width

{

get { return EnemyAnimation.FrameWidth; }

}

// Get the height of the enemy ship

public int Height

{

get { return EnemyAnimation.FrameHeight; }

}

// The speed at which the enemy moves

float enemyMoveSpeed;

public void Initialize()

{

}

public void Update()

{

}

public void Draw()

{

}

Just like the animating the player, we will add information like the Texture and the position for the Enemy in the Initialize() method, as well as, track the speed, health and damage of the enemy object by add the following lines of code to create the enemy :

public void Initialize(Animation animation,Vector2 position)

{    // Load the enemy ship texture

EnemyAnimation = animation;

// Set the position of the enemy

Position = position;

// We initialize the enemy to be active so it will be update in the game

Active = true;

// Set the health of the enemy

Health = 10;

// Set the amount of damage the enemy can do

Damage = 10;

// Set how fast the enemy moves

enemyMoveSpeed = 6f;

// Set the score value of the enemy

Value = 100;

}

Now that the Initialize event is complete, we will add code to the Update() method to manage movement the enemy objects in order to decide if there have gone off screen and deactivate. The enemies created will move based upon the fixed rate, which we will use to update the enemy Animation of its new Position. In order to insure this new position is not off of the screen we will check the new enemy Position to see is past the left edge and/or off the screen. The variable -Width means a value equal to -1 * Width, which is the width of the enemy graphic. The enemy graphic width will be the value of -1*Width when its rightmost pixel touches the left hand side of the screen, therefore the rest of the graphic would be off of the screen at this time. If the enemy is still on the screen but its health has reached zero (or less) then we would also need to make sure that the enemy is removed in the same way as that we do when the enemy object has gone off screen. In order notify the game to deactivate or remove the enemy if either of the aforementioned conditions occur, we will set the Boolean variable, Active, we created as a part of the enemy class object to be false. We will implement logic to check and remove enemy objects based off the Active flag within the root Game1 class.

Therefore, Replace the Update() method with the following code reflecting the scenarios noted:

public void Update(GameTime gameTime)

{

// The enemy always moves to the left so decrement its x position

Position.X -= enemyMoveSpeed;

// Update the position of the Animation

EnemyAnimation.Position = Position;

// Update Animation

EnemyAnimation.Update(gameTime);

// If the enemy is past the screen or its health reaches 0 then deactivate it

if (Position.X < -Width || Health <= 0)

{

// By setting the Active flag to false, the game will remove this objet from the

// active game list

Active = false;

}

}

A simple draw call, just like the way we draw Player. They’re using the same Animation code, and that’s the value of encapsulating functions like animation in their own classes – everything can use the same basic functions.

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

public void Draw(SpriteBatch spriteBatch)

{

// Draw the animation

EnemyAnimation.Draw(spriteBatch);

}

We’re done with our Enemy class for now – we need to get them set up in our game loop, so it’s off to Game1.cs for the rest of this step.

Step 2 – Update the Game class to create Enemy

Switch to the Game1 class by double-clicking the Game1.cs file in the Solution Explorer in Visual Studio. We’re back inside our Game1 class. Now, if we were just interested in a single enemy like we have a single player, things would be pretty simple. We’d add one Enemy class and get it into the Draw() and Update() loops, and be done!

However, in Shooter, there are a lot of enemies. We want to be able to track as many as we want, all moving at the same time. To do this, we need something like an array – we’ll use a special class that acts like an array but can grow and shrink automatically to fit any number of objects we want (within a certain limit). It’s called a List and setting it up isn’t too hard. It also behaves like an array at times; we can walk through it using for loops just like we do with arrays. Look for the first {mark under the start of the Game1 class, and go just below ParallaxingBackground bgLayer2; you added in the previous step. Add a new line and then add the following:

// Enemies

Texture2D enemyTexture;

List<Enemy> enemies;

// The rate at which the enemies appear

TimeSpan enemySpawnTime;

TimeSpan previousSpawnTime;

// A random number generator

Random random;

We’re not just adding a list of enemies, we’re adding some variables that are going to help control the rate at which we add new enemies, as well as a Random class, which spits out random numbers that we can use any time we like; we’ll use them in this game to mix up the vertical position of enemies when they spawn in so they appear to be coming from everywhere. We will need to do some initializing on these new variables. Look down the code, find the Initialize() method. Inside that method, add these lines:

// Initialize the enemies list

enemies = new List<Enemy> ();

// Set the time keepers to zero

previousSpawnTime = TimeSpan.Zero;

// Used to determine how fast enemy respawns

enemySpawnTime = TimeSpan.FromSeconds(1.0f);

// Initialize our random number generator

random = new Random();

Now, since the enemy has graphics associated with it, we’ll need to add a line to the LoadContent() method, the same as we do for the backgrounds and the player. Look down the code, find the LoadContent() method. Inside that method, below the bgLayer2.Initialize call, add these lines:

enemyTexture = Content.Load<Texture2D>(“mineAnimation”);

Now it’s time to get a little more complicated. We’re going to create our own method called AddEnemy(). This is the method we’re going to call when we’re ready to add a new enemy to the game. It will have to do some initialization and position setting. To do this, you’ll need some empty space inside the Game1 class, but not inside any other method. Just like when you added UpdatePlayer(), look for the Update() method, find the first { mark underneath the function name. Then, follow it down until you find a corresponding } mark that’s got the same indentation level. Then, go one line below that, make a new line. Add the following lines to make your new AddEnemy() method:

private void AddEnemy()

{

// Create the animation object

Animation enemyAnimation = new Animation();

// Initialize the animation with the correct animation information

enemyAnimation.Initialize(enemyTexture, Vector2.Zero, 47, 61, 8, 30,Color.White, 1f, true);

// Randomly generate the position of the enemy

Vector2 position = new Vector2(GraphicsDevice.Viewport.Width +enemyTexture.Width / 2,

random.Next(100, GraphicsDevice.Viewport.Height -100));

// Create an enemy

Enemy enemy = new Enemy();

// Initialize the enemy

enemy.Initialize(enemyAnimation, position);

// Add the enemy to the active enemies list

enemies.Add(enemy);

}

This method doesn’t do too much that’s different, except the randomization of the position.Y value – the 100 values are tightening the actual area that the enemy is allowed to spawn in; not too high and not too low, but closer to the center of the screen. Now that we have the adding method, we need to make a method that updates all the enemies we have, as well as determines whether we should add a new enemy based on the amount of time that’s passed. Just like AddEnemy, get some open space inside the Game1 class. Add the following lines to make an UpdateEnemies() method:

private void UpdateEnemies(GameTime gameTime)

{

// Spawn a new enemy enemy every 1.5 seconds

if (gameTime.TotalGameTime – previousSpawnTime > enemySpawnTime)

{

previousSpawnTime = gameTime.TotalGameTime;

// Add an Enemy

AddEnemy();

}

// Update the Enemies

for (int i = enemies.Count – 1; i >= 0; i–)

{

enemies[i].Update(gameTime);

if (enemies[i].Active == false)

{

enemies.RemoveAt(i);

}

}

}

This method does two things – first, it checks the GameTime; if a second or more has passed since an enemy has been added, it adds another enemy and resets the spawn timer. Second, it uses a For loop to walk through all of the current enemies and check their Active flag. Remember the check we made in Enemy.cs that would set Active to false if the enemy went off the screen? Now we check this value and do something with it. We call RemoveAt which takes the Enemy object out of the list, deleting it. By deleting “dead” objects, we keep the amount of memory we use down to a reasonable level. If we didn’t remove the objects at some point, the list would grow forever, eventually maxing out the memory available on your device and causing an error. Conserving memory is important! Now that we’ve written UpdateEnemies(), we need to get our main game loop to call it at some point, so we need to get it into Update(). Look for the Update() method inside the Game1 class, and after the bgLayer2.Update() call, add the following lines:

// Update the enemies

UpdateEnemies(gameTime);

Finally, we’re down to the last method – like the Player and the Background, we need to draw the enemies inside the Draw() method. Now, where the enemies draw is up to you, just as long as it’s after the background. If you draw after the Player, the enemies will appear on top of the player’s ship. If you draw before the Player, the enemies will appear behind the player’s ship. Look for the Draw() method. Inside the method, after you call bgLayer2.Draw(), add these lines:

// Draw the Enemies

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

{

enemies[i].Draw(spriteBatch);

}

This was a big step; Let’s have a look! Build and run your game by pressing CTRL+F5.



Detecting Collisions

This is now shaping up to be a real game now, we have our protagonist (our shooter player) and we have an adversary (the enemy bombs). As any good game developer or video game enthusiast knows, without the conflict between the protagonist and the antagonist; No challenge or reason to play the game exists. Therefore, it is time for us to create conflict and the challenge of the game by adding collisions. The first step is to implement code that will detects whether the player and enemies touch one another i.e. collision detection, and if we detect that a collision exists we can create damage to the player’s health and destruction to the enemy. In order to get our basic collision detection algorithm, we will create the algorithm in Game1 class and update the Player class and Enemy class objects. Switch to the by double-clicking the Game1.cs file in the Solution Explorer in Visual Studio.

Step 1 – Create Collision Algorithm

We are ready to implement our collision detection algorithm. Since it has a few steps to it, we should put it in its own method, as we have done for the Player and Enemy objects. We’ll call it UpdateCollision(). To do this, find some empty space inside Game1 class, not inside any other method. Just like when you added UpdateEnemies(), look for the Update() method, find the first { mark underneath the function name. Then, follow it down until you find a corresponding} mark that has the same indentation level. Then, go one line below that, make a new line and you are ready to go. Add the following lines to make your new UpdateCollision() method:

private void UpdateCollision()

{

    // Use the Rectangle’s built-in intersect function to

    // determine if two objects are overlapping

Rectangle rectangle1;

Rectangle rectangle2;

    // Only create the rectangle once for the player

rectangle1 = new Rectangle((int)player.Position.X,

(int)player.Position.Y,

player.Width,

player.Height);

    // Do the collision between the player and the enemies

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

{

rectangle2 = new Rectangle((int)enemies[i].Position.X,

(int)enemies[i].Position.Y,

enemies[i].Width,

enemies[i].Height);

    // Determine if the two objects collided with each

    // other

If (rectangle1.Intersects(rectangle2))

{

     // Subtract the health from the player based on

     // the enemy damage

     player.Health -= enemies[i].Damage;

        // Since the enemy collided with the player

        // destroy it

     enemies[i].Health = 0;

     // If the player health is less than zero we died

if (player.Health <= 0)

    player.Active = false;

}

}

}

If we take a closer look at what the code is doing; the general idea behind collision detection between objects is very straightforward: it is by definition if any each pixel of an sprite and/or image touches and/or collides with any pixel of the another sprite/image.

Hence, we must remember that a sprite/image is always a rectangle despite that most sprites/images are really not rectangular given this most images contain a lot of transparent pixels. Ultimately, our Player and Enemy objects are graphically represented by a rectangular texture on the screen. Using that knowledge we can detect collision that checks whether the rectangle of the Player object graphic and the rectangle of the Enemy object graphic overlap, intersect, or touch at any point. If they are, they have “collided” and the game can respond to this collision by decrementing the health of the player based upon the damage measurement of the enemy object and then proceeding to destroying the enemy object. We use the Rectangle class helper methods to if two rectangles are intersecting. We simply set the size and position of the rectangles to be the same size and position as the objects in our game, and do the intersection testing from there. Please note: The more curved an object is drawn; the less accurate this collision will be, because the pixels the user sees will not correspond with the rectangles that are colliding. For better accuracy, we can still leverage this collision detection algorithm by using other bounding shapes squares, triangles or circles or by implementing a pixel-perfect collision system which first check whether the rectangle bounds of two sprites intersect, then check whether any pair of pixels within the intersection are non-transparent on both sprite textures, and if the intersection of one pair of non-transparent pixels is found a collision is detected.

Now that we know a little more about collision detections, the only step left to implement collisions in our game is to place the UpdateCollision() method inside the game loop’s Update() call within the Game class. To complete this go to the Update() method inside the Game1 class, and after the UpdateEnemies() call, add the following code:

// Update the collision

UpdateCollision();

We’ve finished now all that is left to do is to run the game, and if the collision implementation is working you will notice enemies disappearing when your ship runs into them. Right now in the game even though we are decreasing the health of the player we have not put code into the game to have the player “die” even though our collision code above makes a change to Player.Active when their health drops below zero. We will have to have the Active property of the Player class checked by our game before that happens.

In this walkthrough, we went through the steps to create enemies and to implement a collision detection system. In Part 7 of this series, we will add logic and code to add adding projectiles so that our player can fire “bullets” to destroy enemies and put this collision system to good use. We will also add sound and effects to make our game come to life.