Windows 8 Game Development using C#, XNA and MonoGame 3.0: Building a Shooter Game Walkthrough – Part 5: Animating the Player/Ship and Creating a Parallaxing Background

Overview

In Part 4 of this blog series, we added the ability for the user to interact with the game using all forms of User Input, (i.e. Touch, Mouse, GamePad, etc.). Now, we need to make the game much more graphically astute and provide a more pleasurable experience. To do this we need to add some animation and a more realistic background to the game. When we added the graphics in Part 2 of this series, we included a sprite sheet which has eight (8) pictures of different states of the player ship. We will now use this to create an animation for the ship's movement. If we treat each of the different picture states of the ship as frames, we can cycle through each frame continuously throughout the game loop to given the illusion of ship fluttering; thus providing animation to the game. In order to provide a realistic background to the Shooter game, we need to simulate the sky with clouds moving as the ship flies through the background.

Create Animation class and methods

So now let's add animation to the ship. In order to do this, create a class called Animation. Add a new class to your project by right clicking on the Win8Shooter project and selecting Add and New Item, select Class or by pressing SHIFT + ALT + C, and typing in the name Animation.cs. Press ENTER.

Inside our new Animation.cs code file, replace a few lines at the very top. Delete all the using statements at the top, and replace them with the following lines:

using System;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Content;

using Microsoft.Xna.Framework.Graphics;

Place your cursor just after the {mark inside the Animation class. Add a new line and add the following lines:

// The image representing the collection of images used for animation

Texture2D spriteStrip;

// The scale used to display the sprite strip

float scale;

// The time since we last updated the frame

int elapsedTime;

// The time we display a frame until the next one

int frameTime;

// The number of frames that the animation contains

int frameCount;

// The index of the current frame we are displaying

int currentFrame;

// The color of the frame we will be displaying

Color color;

// The area of the image strip we want to display

Rectangle sourceRect = new Rectangle();

// The area where we want to display the image strip in the game

Rectangle destinationRect = new Rectangle();

// Width of a given frame

public int FrameWidth;

// Height of a given frame

public int FrameHeight;

// The state of the Animation

public bool Active;

// Determines if the animation will keep playing or deactivate after one run

public bool Looping;

// Width of a given frame

public Vector2 Position;

public void Initialize()

{

}

public void Update()

{

}

public void Draw()

{

}

You'll notice, much like the Player class, we have an Initialize() , Update() , and Draw() method; animations need all three to be able to work right. Let's start by filling out the Initialize() method. We will add some parameters that we can pass in each time we create a new animation, and then we will store the parameters to control the animation.

Replace the Initialize() method you just filled in with this version:

public void Initialize(Texture2D texture, Vector2 position, int frameWidth, int frameHeight, int frameCount, int frametime, Color color, float scale, bool looping)

{

// Keep a local copy of the values passed in

this.color = color;

this.FrameWidth = frameWidth;

this.FrameHeight = frameHeight;

this.frameCount = frameCount;

this.frameTime = frametime;

this.scale = scale;

 

Looping = looping;

Position = position;

spriteStrip = texture;

 

// Set the time to zero

elapsedTime = 0;

currentFrame = 0;

 

// Set the Animation to active by default

Active = true;

}

When we initialize a class object, we generally assign the object's variables to copies of the values that are coming in from the parameters. This will come in very handy as we use this Animation class more and more – you can call Initialize() once for every object you want to use animation with different graphics, timings, and sizes, and each Animation object can then run on its own using the data you've passed in. You can have an animation for every object in your game. Like the Update() methods in the Game1 class, this method operates on GameTime – we'll use this method to do the heavy lifting to decide whether or not enough time has passed in the animation to switch it to the next frame, and do the calculation to turn the frame count into the actual pixels we want to draw on the screen. Replace the Update() method in Animation.cs you just filled in above with this version:

public void Update(GameTime gameTime)

{

// Do not update the game if we are not active

if (Active == false) return;

// Update the elapsed time

elapsedTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds;

// If the elapsed time is larger than the frame time

// we need to switch frames

if (elapsedTime > frameTime)

{

// Move to the next frame

currentFrame++;

 

// If the currentFrame is equal to frameCount reset currentFrame to zero

if (currentFrame == frameCount)

{

currentFrame = 0;

// If we are not looping deactivate the animation

if (Looping == false)

Active = false;

}

 

// Reset the elapsed time to zero

elapsedTime = 0;

}

// Grab the correct frame in the image strip by multiplying the currentFrame index by the

Frame width

sourceRect = new Rectangle(currentFrame * FrameWidth, 0, FrameWidth, FrameHeight);  

// Grab the correct frame in the image strip by multiplying the currentFrame index by the frame width

destinationRect = new Rectangle((int)Position.X - (int)(FrameWidth * scale) / 2,

(int)Position.Y - (int)(FrameHeight * scale) / 2,

(int)(FrameWidth * scale),

(int)(FrameHeight * scale));

}

All of the information about which pixels of the sprite sheet need to be drawn is stored in the sourceRect and destinationRect objects. This is what the Draw() method will consume to draw the right frame to the screen. Let's complete the Draw() method and our work in this class will be done. Replace the Draw() method you just filled in above with this version:

// Draw the Animation Strip

public void Draw(SpriteBatch spriteBatch)

{

// Only draw the animation when we are active

if (Active)

{

spriteBatch.Draw(spriteStrip, destinationRect, sourceRect, color);

}

}

Now, any object we want to draw on the screen can be animated using this class. Right now, the game is using a simple Texture2D to represent the player graphically on screen, but we can now leverage this class to animate the player graphic on the screen by cycling through each frame of the sprite sheet using the Animation class.

Update the Player class to use Animation

Now let's use this Animation class with the Player object to give our player graphic a bit of motion. So switch to the Player class by double-clicking the Player.cs file in the Solution Explorer in Visual Studio. Let's start by replacing all the correct namespace for the Animation class in the Player.cs file by adding the following line of code under the last using statement:

using Win8ShooterGame;

Near the top of the Player.cs file, just after the first { mark after the Player class begins, comment out the public Texture2D PlayerTexture; and add the following line underneath:

//public Texture2D PlayerTexture;

public Animation PlayerAnimation;

Now that we've removed the old Texture2D, there are a number of methods that won't work if we tried to build and run right now. We need to go change them, starting with the Initialize() method. Scan down the Player.cs file and find the Initialize() method. Replace it with the following:

public void Initialize(Animation animation, Vector2 position)

{

PlayerAnimation = animation;

// Set the starting position of the player around the middle of the screen and to the back

Position = position;

// Set the player to be active

Active = true;

// Set the player health

Health = 100;

}

We also have two properties that are relying on the old texture data. We need to change them. Find the Width and Height properties in the Player class, and replace them with the following:

public int Width

{

get { return PlayerAnimation.FrameWidth; }

}

 

// Get the height of the player ship

public int Height

{

get { return PlayerAnimation.FrameHeight; }

}

Find the Draw() method and replace it with the following:

public void Draw(SpriteBatch spriteBatch)

{

PlayerAnimation.Draw(spriteBatch);

}

Finally, find the Update() method in the Player class and replace it with the following:

// Update the player animation

public void Update(GameTime gameTime)

{

PlayerAnimation.Position = Position;

PlayerAnimation.Update(gameTime);

}

Update the Game class to use Animation

The only step left is to tell the game to create the Animation and Initialize it with a sprite strip graphical file. The appropriate place to do this is in the LoadContent() method, where we were previously loading the static texture. Find the LoadContent() method in the Game.cs file.

Find the line marked player.Initialize(Content.Load<Texture2D>("player"), playerPosition) and replace it with the following:

// Load the player resources

Animation playerAnimation = new Animation();

Texture2D playerTexture = Content.Load<Texture2D>("Graphics\\shipAnimation");

playerAnimation.Initialize(playerTexture, Vector2.Zero, 115, 69, 8, 30, Color.White, 1f, true);

 

Vector2 playerPosition = new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X,

GraphicsDevice.Viewport.TitleSafeArea.Y

+ GraphicsDevice.Viewport.TitleSafeArea.Height / 2);

player.Initialize(playerAnimation, playerPosition);

And finally, we need to pass the timing from the game loop's Update() method into the Player class, so its Update() method can pass that onto the Animation class. To do this, we'll put a line in the UpdatePlayer() . Find the UpdatePlayer() method. At the top of the method, add the following line:

player.Update(gameTime);

You should still be able to move the ship around just as in the previous step, but now, you should see some fluttering effects from the sails and steam from the back of the ship.

Creating and Drawing a Parallaxing Background

Our next step is to perform a bit of visual trickery to give the impression that we are not simply floating in blue space, but rather racing through clouds. We could just draw a single cloud background and move it from right to left, but we can do better. We will draw a parallax background. Parallax is a term used for drawing several layers of images that move at different speeds, giving the illusion of depth

Step 1 – Create Parallax Background class

Create a class for this new type of parallaxing background object, write all the necessary functionality inside it, and then link it up inside our Game1 class. Add a new class to your project by pressing SHIFT + ALT + C, and typing in the name ParallaxingBackground.cs. Press ENTER.

Inside our new code file, the first thing to do is replace a few lines at the very top. Delete all the using statements at the top, and replace them with the following lines:

 

// ParallaxingBackground.cs

using System;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Content;

using Microsoft.Xna.Framework.Graphics;

As we did in the Animation class, we'll set up all the data we'll need and create in our standard Initialize(), Update(), and Draw() methods to fill in later. Place your cursor just after the { mark inside the ParallaxingBackground class. Add a new line and add the following lines of code:

// The image representing the parallaxing background

Texture2D texture;

// An array of positions of the parallaxing background

Vector2[] positions;

// The speed which the background is moving

int speed;

 

public void Initialize()

{

}

 

public void Update()

{

}

 

public void Draw()

{

}

Step 2 – Update Parallax Background class to Create Scrolling Background

As you can see, it is not a whole lot of data. It has one Texture2D, and an array of Vector2 objects, as shown by the [] marks. An array is a way of storing multiple copies of the same type of object and operating on them quickly. You will see more of these as we go. Finally, we use an int to determine the speed of the background. Like we showed in the diagram above, we'll have two parallaxing layers on top of a static background – so in the end, we'll make two of these ParallaxingBackground objects inside the Game1 class and then a basic Texture2D for the static part. The next thing to do is start filling out the methods of this class. Let's start with Initialize(). We'll write some code that takes in a path to a graphical file and some size and speed parameters to fill out the class data. Replace the Initialize() method with the following lines of code:

public void Initialize(ContentManager content, String texturePath, int screenWidth, int screenHeight, int speed)

{

bgHeight = screenHeight;

bgWidth = screenWidth;

 

// Load the background texture we will be using

texture = content.Load<Texture2D>(texturePath);

// Set the speed of the background

this.speed = speed;

// If we divide the screen with the texture width then we can determine the number of tiles need.

// We add 1 to it so that we won't have a gap in the tiling

positions = new Vector2[screenWidth / texture.Width + 1];

// Set the initial positions of the parallaxing background

for (int i = 0; i < positions.Length; i++)

{

    // We need the tiles to be side by side to create a tiling effect

positions[i] = new Vector2(i * texture.Width, 0);

}

}

First, we're calling Load() to initialize the requested graphic from disk , we've normally done this inside Game1.LoadContent() . We're simply passing in the ContentManager that does the loading instead. Next, we're filling in the array and giving it a length of new Vector2[screenWidth / texture.Width + 1]. In this case, it is dividing the whole screen size by the size of the texture we are using for the background, and then adding 1, so we will always have enough tiles of background plus one more sitting off the edge of the screen so it'll appear to smoothly scroll in. We instantiated the array, but we need to set the values of the Vector2 objects inside the array. The for loop is how we create the objects and set each to be a Vector2 with an X value of i * texture.Width, and a Y value of 0. Inside the Update() method, we're going to move our background by changing its Position vector – similar to how we move the player's ship inside the Player class. Inside the Update() method enter the following lines of code:

public void Update(GameTime gametime)

{

    // Update the positions of the background

for (int i = 0; i < positions.Length; i++)

{

    // Update the position of the screen by adding the speed

positions[i].X += speed;

// If the speed has the background moving to the left

if (speed <= 0)

{

        // Check the texture is out of view then put that texture at the end of the screen

if (positions[i].X <= -texture.Width)

{

positions[i].X = texture.Width * (positions.Length - 1);

}

}

// If the speed has the background moving to the right

else

{

     // Check if the texture is out of view then position it to the start of the screen

if (positions[i].X >= texture.Width * (positions.Length - 1))

{

positions[i].X = -texture.Width;

}

}

}

}

We're moving each tile of the background along its x-axis – that's horizontal movement. If the speed is positive, we increase the position in the positive x-axis, which moves the tile to the right. If the speed is negative, we adjust the position in the negative x-axis, moving the tile to the left. If a tile has scrolled completely past the end of the screen, we reset it to the beginning. If you think about what we're doing graphically, it's just a visual trick done off-screen. Because we always have one more tile of background that's outside the screen, we just keep swapping it around; when the tile goes from right to left completely and disappears off screen, we reset it back to outside the screen on the right, so it will slide back into view all over again. Since there's already a tile in view, the player never notices the change. This loop will go on forever and give a feeling of smooth animation with no gaps. We're done updating, now we need to Draw() the background. We will create a rectangle that will be the base for the background image that we will draw update with the new position as it moved. Then we will draw texture within the rectangle in the Draw method. To accomplish this replace the Draw() method with the following code:

public void Draw(SpriteBatch spriteBatch)

{

for (int i = 0; i < positions.Length; i++)

{

Rectangle rectBg = new Rectangle((int)positions[i].X, (int)positions[i].Y, bgWidth, bgHeight);

spriteBatch.Draw(texture, rectBg, Color.White);

}

}

Step 3 – Update Game.cs class to use Parallax Background class/object

We now need to create a couple of these background objects in our Game1 class, and integrate them into the Game1 class methods of Initialize(), LoadContent(), Update(), and Draw() to get them into the game. Switch to the Game1 class by double-clicking the Game1.cs file in the Solution Explorer in Visual Studio. Look for the first { mark under the start of the Game1 class, and go just below the float playerMoveSpeed you added in the input step. Add a new line and then add the following:

// Image used to display the static background

Texture2D mainBackground;

Rectangle rectBackground;

float scale = 1f;

There's our two parallaxing layers and the static third background layer. Now, let's initialize them. Look down the code, find the Initialize() method. Inside that method, below the player = new Player(); add these lines:

//Background

bgLayer1 = new ParallaxingBackground();

bgLayer2 = new ParallaxingBackground();

So far, so good. Now, we'll load the appropriate textures in from disk in the LoadContent() method. Look down the code, find the method called protected override void LoadContent(). Inside that method, below the player.Initialize call, add these lines of code to method:

// Load the parallaxing background

bgLayer1.Initialize(Content, "Graphics/bgLayer1", GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, -1);

bgLayer2.Initialize(Content, "Graphics/bgLayer2", GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, -2);

 

mainBackground = Content.Load<Texture2D>("Graphics/mainbackground");

Just two more to go – we need to add code to Update and Draw. Look for the Update() method inside the Game1 class, and after the UpdatePlayer call, add the following lines:

// Update the parallaxing background

bgLayer1.Update(gameTime);

bgLayer2.Update(gameTime);

And finally, we'll draw the static background, then the two parallaxing backgrounds on top of it. Careful here! The order in which you run these methods is very important. You want to draw the background first, then draw the player afterward. If you do it backwards, the background will draw right on top of the player and hide them. Look for the Draw() method. Inside the method, before you call player.Draw() , add these lines:

//Draw the Main Background Texture

_spriteBatch.Draw(mainBackground, rectBackground, Color.White);

 

// Draw the moving background

bgLayer1.Draw(_spriteBatch);

bgLayer2.Draw(_spriteBatch);

 

In conclusion, this walkthrough demonstrated how to create an animation class that can be used to animate textures by cycling through the frame sprite sheets. We used this class to animate our player texture and make our ship behave realistically. Afterwards, we went through the steps to create a base background and create the illusion of the sky moving within the game by creating a parallaxing background.

In Part 6 of this series, we will add logic to create enemies and animate them through the game, in order to present a challenge for the player of the game.