XNA Game Making

Resources for making games with XNA

Chapter 7: Creating a Player Class

 To get the free pdf or to buy a printed version go to the book page at  lulu.com.

When I first taught a course on XNA after showing how to draw a sprite on the screen I immediately went on to reading in the keyboard state and moving the sprite around on the screen. Then a few classes later when we started to make larger games we changed the program so that the sprites were no longer being stored in the main game class but in separate classes, such as storing the picture of the player in a player class instead of in the game class. This proved to be very confusing for people, and most wanted to just keep the sprites all in the game class. So now, in order to avoid confusion later, we'll start making a separate class to store our sprite for the player in, which is more Object-Oriented and is the way larger games are made.

 

The class we'll be making is the basics of the Player class for the Ship Fighter game. This class will be pretty simple: It will draw a sprite for the player ship on the screen and move it around. We'll also look at some drawing options to give more details when we're rendering sprites. We will:

l  Create a separate player class for our player sprite.

l  Put in more sprite attributes

l  Learn how to read input in XNA

 

Creating a Player Class

Creating a New Project

We’ll start creating the player class for the Ship Fighter game. Create a new solution just like we did in the previous chapter by going to File->New Project and select Windows Game (3.1). Name the project SimplePlayer. Get a copy of shipSprite.png and spaceBackground.tga from xnagamemaking.com and copy them to the Content folder of SimplePlayer and add them to the project. (For more details on how to do this look at the creation of the SpriteDisplay solution in the previous chapter. Actually, to keep the background go through the steps in that chapter again to draw the background picture.)

 

To create the player class the first step is to add a new CS file for the player. Right click on the project name (SimplePlayer) in the solution Explorer and select Add->New Item...

 

 

From the Add New Item dialog select Class and give it the name Player and click OK

 

 

This will create a holder for the Player class. The following code is automatically generated in Player.cs:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace SimplePlayer

{

    class Player

    {

    }

}

 

The first thing that we'll do is add in the XNA using statements so that we can access XNA code in this file too. Add the following below the System.Text:

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Audio;

using Microsoft.Xna.Framework.Content;

using Microsoft.Xna.Framework.GamerServices;

using Microsoft.Xna.Framework.Graphics;

using Microsoft.Xna.Framework.Input;

using Microsoft.Xna.Framework.Media;

using Microsoft.Xna.Framework.Net;

using Microsoft.Xna.Framework.Storage;

 

You really don't need all of these using directives, but like we said before we'll just copy all of them to keep things simple. We now have the Player.cs file setup and ready to start adding code.

 

Player setup

In the last chapter we went over four things to add a variable to the game class in XNA:

 

  1. Declare the variable
  2. Initialize the variable, usually in the LoadContent() method
  3. Add the item to our Draw loop so we can see it onscreen
  4. Add the item to our Update loop so we can do any logic updates on it

 

For our player we'll be following this pattern. First we'll create the member for the player in Game.cs, then initialize the player, and then add drawing and updating to the Player. The two files we'll be working with here are Player.cs and Game1.cs.

 

Declare the Player

The first step is easy, in the Game1.cs file or our project before the public Game1() line add the following:

 

Player playerShip;

 

This says we are declaring a new object for our game class, a Player object called playerShip. Note the term Player is because we named our player class Player. The playerShip is the name of the instance of our Player class, and we can call this anything we want. Our player object is now in our game, but it isn't initialized (loaded) yet.

 

 

Initialize the Player

Before initializing the player we need to decide what information we need to track about the player. Our goal for the moment is to just draw a player ship on the screen and move it around, so we'll only need a few member variables for the player. In the player class we'll create variables for the sprite (picture to draw), a Vector2 for the sprite's position onscreen, and the sprite origin. The sprite origin remember is where on the sprite it will center the drawing, and we'll want to keep this in the center of the sprite (this means if we tell the sprite to be drawn at coordinate (200,200) on the screen the center of the sprite will be at (200,200), instead of putting the upper left corner of it there.) In addition to these we'll store the window width and height we're drawing the sprite in, in case we need it later.

 

So in our player class add the following variables:

 

Vector2 position;

Texture2D shipSprite;

Vector2 spriteOrigin;

int windowWidth, windowHeight;

 

Next let's create a constructor for the Player, which will initialize these variables:

 

// This method is called when the Player is created

public Player(GraphicsDevice device, Vector2 position, Texture2D sprite)

{

// The position that is passed in is now set to the position above

this.position = position;

 

// Set the Texture2D

shipSprite = sprite;

 

// Setup origin

spriteOrigin.X = (float) shipSprite.Width / 2.0f;

spriteOrigin.Y = (float)shipSprite.Height / 2.0f;

           

// Set window dimensions

windowHeight = device.Viewport.Height;

windowWidth = device.Viewport.Width;

}

 

This constructor takes in a GraphicsDevice, position, and Texture2D. For the player's position we set the position passed in as our start position. Then for the shipSprite we set it to the sprite passed in. For the origin we just take the center of the shipSprite, which is found by taking its width and height and dividing by two:

 

spriteOrigin.X = (float) shipSprite.Width / 2.0f;

spriteOrigin.Y = (float) shipSprite.Height / 2.0f;

 

Then for the window width and height we look at the graphics device's viewport (drawing area) width and height:

 

windowHeight = device.Viewport.Height;

windowWidth = device.Viewport.Width;

 

That sets up our player constructor. Let's initialize the player back in the game class. Change the LoadContent method in the Game1.cs to this:

 

protected override void LoadContent()

{

spriteBatch = new SpriteBatch(GraphicsDevice);

spaceTexture = Content.Load<Texture2D>("spaceBackground");

Texture2D playerTexture = Content.Load<Texture2D>("shipSprite");

playerShip = new Player(GraphicsDevice, new Vector2(400, 300),

playerTexture);

}

 

The first thing new here is the loading of the sprite for the player ship. Note that we declare this as a local variable since we won't be using the playerTexture in the Game1.cs file any after we create the player. Then we initialize the player by giving it the current GraphicsDevice, a start position Vector2 (which doesn't have to be (400,300), this was just chosen because it puts it in the center), and the sprite for the player ship.

 

If you try running the program now by going to Debug->Start Debugging you'll find that nothing seems to happen; it still just shows the background from last time but no ship. This is because we have initialized the player but we are not drawing or updating it yet.

 

Drawing the Player

To draw the player we’ll first add a draw method to the player class. Right below the Player constructor method in Player.cs add the following:

 

 // Draw the player

public void Draw(SpriteBatch batch)

{

batch.Draw(shipSprite, position, null, Color.White,

                    0.0f, spriteOrigin, 1.0f, SpriteEffects.None, 0.0f);

}

 

This method takes in a SpriteBatch and then uses it to draw the shipSprite on the screen. As you can see the batch.Draw looks a bit different than the ones we've seen before. We'll go over the changes in a minute, but for now change the Draw method in Game1.cs to:

 

protected override void Draw(GameTime gameTime)

{

 

GraphicsDevice.Clear(Color.CornflowerBlue);

 

spriteBatch.Begin();

 

spriteBatch.Draw(spaceTexture, Vector2.Zero, Color.White);

 

playerShip.Draw(spriteBatch);

 

spriteBatch.End();

 

base.Draw(gameTime);

}

 

The key line here is the:

 

playerShip.Draw(spriteBatch);

 

This calls the player's draw method and tells it to draw the ship sprite on the screen. If you run the program you'll see this:

 

 

We're drawing the ship in our game by putting it in the draw loop and adding a draw method to the Player class. Next we'll look at that batch.Draw method in the Player class again.

 

Sprite Attributes

The draw method we used this time was:

 

batch.Draw(shipSprite, position, null, Color.White,

                    0.0f, spriteOrigin, 1.0f, SpriteEffects.None, 0.0f);

 

This matches up with this definition:

 

public void Draw (

         Texture2D spriteName,

         Rectangle destinationRectangle,

         Color tint,

         float rotation,

         Vector2 origin,

         SpriteEffects effects,

         float layerDepth

)

 

The first parameter, spriteName, is the name of the sprite to draw. The second parameter is for a destination rectangle. If we want just part of the image to be drawn we specify a rectangle to be the size of the sprite on screen. If you try replacing the null in our code with the line new Rectangle(0,0,40,100) and run the program you'll see only half of the spaceship, since the destination rectangle is half the size of our original image. This parameter we'll keep at null most of the time.

 

The third parameter is for adding a tint to our image. The parameter is a color, (if you type Color. a list of colors will be available.) We don't want a tint so we'll just keep the color white. The rotation allows us to set an angle of rotation for the sprite,  if we want it a different orientation. The next parameter is a vector for the sprite origin. Remember for a spaceship sprite that we move along the screen we don't think of the spaceship's position as where its upper left corner, so we'll put the origin at the center of the spaceship sprite. This way the position of the spaceship means its center.

 

The SpriteEffects parameter allows us to flip the picture if we want to (the enumeration is FlipHorizontally and FlipVertically) but, again, we'll leave that on None. The last parameter is the layer depth. The sprite depth, if you recall, is the order in which the sprites are drawn on top of each other, with those closest to 0.0 being nearer to the top, and those close 1.0 being at the bottom. So we give the spaceship a depth of 0.0 since it's on top and the background a depth of 1.0.

 

We have the ship drawing, now for moving it around.

 

Updating the Player

Our goal for the player is to have the sprite moving around based on input. First we’ll put in the basic structure for the update. In the player class add the following method below the Draw() one:

 

// Update - for animation

public void Update(GameTime gameTime)

{

 

}

 

And change the Update loop in the Game1 class to update the player too:

 

protected override void Update(GameTime gameTime)

{

     // Allows the game to exit

if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==

ButtonState.Pressed)

this.Exit();

 

playerShip.Update(gameTime);

 

base.Update(gameTime);

}

 

We'll be using this a little later. One thing we can do now is creating some methods to change the ship's position. From Game1.cs we'll be getting keyboard and game controller input, and based on that we'll want to call some functions to move the player sprite around. So add the following four methods to the Player class:

 

 // These match up with the Arrow keys

public void Accelerate()

{

position.Y -= 3.0f;

}

 

public void MoveReverse()

{

position.Y += 3.0f;

}

 

public void TurnRight()

{

position.X += 3.0f;

}

 

public void TurnLeft()

{

position.X -= 3.0f;

}

 

These methods move the shipSprite around on the screen by changing its position. The first two, Accelerate and MoveReverse, move the shipSprite up and down the screen respectively. Note to move the shipSprite up the screen we subtract from its Y coordinate. Since the zero for Y is at the top of the screen subtracting Y moves up and adding to Y moves down. Turning left and right change the X position. Now we are setup for input.

 

XNA Input

For input in XNA we have three basic options, the keyboard, Xbox controller, and mouse (we won't worry about the mouse here.) For getting input XNA works a little bit differently than a lot of systems. XNA uses a polling method instead of an event driven method. An event driven method is one where the user does something to generate input, like pressing a key on a keyboard, and then a message is sent to the program letting it know the input event just happened. XNA's polling method does not generate events like this; what XNA does is at every update of the Update loop you can request the current keyboard state. This state is a snapshot of the keyboard and you can check this snapshot of the keyboard and see what keys are down. This is faster, but we'll need to be careful later when we want to see if buttons are pressed that toggle options. To see how this works copy the following UpdateInput method into the Game class below the Update loop.

 

private void UpdateInput()

{

KeyboardState keyState = Keyboard.GetState();

GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);

 

if (keyState.IsKeyDown(Keys.Up)

  || gamePadState.DPad.Up == ButtonState.Pressed)

{

playerShip.Accelerate();

}

if (keyState.IsKeyDown(Keys.Down)

  || gamePadState.DPad.Down == ButtonState.Pressed)

{

playerShip.MoveReverse();

}

if (keyState.IsKeyDown(Keys.Left)

  || gamePadState.DPad.Left == ButtonState.Pressed)

{

playerShip.TurnLeft();

}

if (keyState.IsKeyDown(Keys.Right)

  || gamePadState.DPad.Right == ButtonState.Pressed)

{

playerShip.TurnRight();

}

}

 

In the main Game update loop add the line:

 

UpdateInput();

 

This works just like we described. The first two lines get the current state of the keyboard and (optionally) Xbox controller:

 

KeyboardState keyState = Keyboard.GetState();

GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);

 

For the Gamepad (Xbox controller) we can get the state of up to four controllers, so we tell it PlayerIndex.One to get the state of the first one.

 

After this we just query if a key or gamepad D button is currently pressed. For the keyboard we use

 

keyState.IsKeyDown(Keys. )

 

Which says if the key is down of the argument we pass in. The Keys is an enumeration of all the keyboard keys (type in Keys. and wait for intellisense to show a list of all possible keys.) For the Gamepad we just check the state of the DPad (directional pad) and see if its state is pressed too. We'll be using more Xbox controller buttons later, if you're curious about how to check the rest for now look in the XNA documentation.

 

If you run the solution now you'll be able to move the ship around on the screen with the keyboard or gamepad. This is quite a big start for our game, having a player class setup and moving a player around onscreen. Next we'll add a few things to it.

 

Extending the Player

One simple thing we can add to the player class is to keep the player always on screen. Currently you can move the player completely off screen, but it'd be nice to keep it always visible. And right now the ship moves up/down and left/right just at a constant velocity (speed) of 3 pixels per update. It'd be nice to make this be a bit more realistic, having the ship accelerate and decelerate with changing speeds. We'll do some very simple physics to put that in. This solution is just an extension of SimplePlayer, but is listed as Simple Player Extended on xnagamemaking.com.

 

Keeping Onscreen

To keep the ship onscreen we'll make use of the window width and height variables that we put in earlier. All we'll do is change the Update method of the Player to the following:

 

public void Update(GameTime gameTime)

{

if (position.Y < 0) position.Y = 0.0f;

if (position.Y > windowHeight) position.Y = windowHeight;

if (position.X < 0) position.X = 0.0f;

if (position.X > windowWidth) position.X = windowWidth;

}

 

At each update we're checking to see if the X or Y coordinate of the position is below zero, which is off-screen, and if it is just setting the position to zero. And if the X or Y is greater than the window size, which is also off-screen, we max out the position to it. We are locking max and min positions so the ship is always onscreen now.

 

Putting in Simple Physics

On to putting in acceleration/deceleration. We're not actually putting in real physics here, just some quick hacks to give the ship a physics feel. What we'll do is create a variable called velocity that tracks how fast the ship is going. At each update we'll add to the vertical position the velocity times how much time has padded. And though in space there is no resistance (slow down) we'll go ahead and put in a resistance that shrinks the velocity at each update. This will give us an acceleration and deceleration for the ship.

 

So first in the Player class add a new variable called velocity below the windowWidth and windowHeight variables:

 

 // Up/Down speed

float velocity = 0.0f;

 

This initializes the velocity to zero, which makes sense since the ship won't start out moving. Then change the Accelerate() and MoveReverse() methods to just change the velocity directly:

 

public void Accelerate()

{

    velocity -= 20.0f;

}

 

public void MoveReverse()

{

    velocity += 20.0f;

}

 

The choice of 20 is completely arbitrary. The units here are just pixels per second and 20 felt like a good choice. Next we'll put in the position update. This should go right before our window bounds check in the Player update:

 

position.Y += velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;

velocity *= 0.95f;

 

This does what we just said it would. The first line adds to the current position the velocity time the elapsed time. The second line shrinks the velocity by multiplying by 0.95 at each update. Again, the choice of 0.95 is arbitrary; feel free to play with different values for it.

 

Summary

We have the start of our Ship Fighter game setup. We have a player class setup and can move it around onscreen, and saw how to add two simple adjustments to it. In the next chapter we'll put in a multi-level scrolling background for it.

NEXT