Part 1 - The Player
Class
In this walkthrough, create a new Player
class and use it to allow the player to control a character on the screen.
Defining the Player
Class
Define a Player
class that will represent the game component that the user will control.
Setup
There is some essential setup necessary when defining a new class.
- Create a new file named Player.cs in the "src" folder
- At the top of the file, add two
using
statements that include the proper MonoGame tools:using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics;
- Under the
using
statements, create anamespace
wrapper to ensure that thePlayer
class will be included in the same namespace as theArcadeFlyerGame
class:namespace ArcadeFlyer2D { }
- In the body of the
namespace
wrapper, define a new class namedPlayer
:class Player { }
Now the Player
has been defined! It does not do much of anything yet, but the basic setup is complete.
Fields
The Player
class should hold some sort of data so that a Player
object can actually be displayed on the screen.
One thing the Player
will need is a reference to the game that contains it. This can facilitate communication between the player and the overarching game structure. In the body of the Player
class, add a field named root
with a type of ArcadeFlyerGame
:
private ArcadeFlyerGame root;
Another thing to track is the player's position on the screen. This can be stored in a Vector2
structure in MonoGame. This structure simply holds two float
values; an X
and a Y
. These can be used to represent the player's position. Add the field in the body of the Player
class:
private Vector2 position;
Next, the player should have a Texture2D
image. Add this field under the position
field:
private Texture2D spriteImage;
Another important thing to store will be the width of the sprite as it appears on the screen. The sprite may need to resize the image to make it fit. Add this field under the spriteImage
field:
private float spriteWidth;
Now the Player
class actually has some data associate with it!
Constructor and Content Loading
Next, add some initialization to the Player
class that will build the player object.
- Define a constructor for the
Player
class that takes in aroot
parameter of typeArcadeFlyerGame
, and aposition
parameter of typeVector2
:public Player(ArcadeFlyerGame root, Vector2 position) { }
- In the body of the constructor, initialize the
root
andposition
fields withthis.root
andthis.position
:this.root = root; this.position = position;
- Under those statements, set the
spriteWidth
field to128.0f
:this.spriteWidth = 128.0f;
- Under that, call a method named
LoadContent
that has yet to exist:LoadContent();
- Time to define the method! In the
Player
class, define a new method namedLoadContent
with no parameters and avoid
return type:public void LoadContent() { }
- In the body of the
LoadContent
method, use theroot
field to load theTexture2D
"MainChar" asset - Set the
spriteImage
property to the loadedTexture2D
:this.spriteImage = root.Content.Load<Texture2D>("MainChar");
Now, the class has a way to properly initialize a new Player
object!
Properties
Next, there are a couple of calculated properties that will be necessary for the player. Properties in C# behave like fields, but they actually have special accessor methods that can do some interesting things.
SpriteHeight
The SpriteHeight
property will calculate the height of the sprite on the screen based on the height of the actual image asset, and the resizing scale from the width.
- In the
Player
class, create afloat
property namedSpriteHeight
:public float SpriteHeight { }
- In the body of the
SpriteHeight
property, create aget
accessor:get { }
- The code in the body of the
get
accessor will determine how the property returns its value; first, define a variable to calculate the scale:float scale = spriteWidth / spriteImage.Width;
- Return the adjusted height value:
return spriteImage.Height * scale;
That's it for the SpriteHeight
property! This property will only be gettable; it will not be settable.
PositionRectangle
The position rectangle for the player represents the rectangle where they will appear on the screen. This can also be calculated based on the position
field, the spriteWidth
field, and the SpriteHeight
property. Note that the Rectangle
object must be created using int
values, so the values from the Player
class will have to be casted.
- In the
Player
class, create aRectangle
property namedPositionRectangle
:public Rectangle PositionRectangle { }
- In the body of the
PositionRectangle
property, create aget
accessor:get { }
- In the body of the
get
accessor, return a newRectangle
with the properly casted x-coordinate, y-coordinate, width, and height (based on thePlayer
values):return new Rectangle((int)position.X, (int)position.Y, (int)spriteWidth, (int)SpriteHeight);
That's it for the PositionRectangle
property! Like the SpriteHeight
property, this property will only be gettable; it will not be settable.
Updating and Drawing
Now the Player
class has everything it needs to exist in the game. It's time to add functionality to make that happen!
- In the
Player
class, define a new method namedUpdate
with avoid
return type and aGameTime
parameter:public void Update(GameTime gameTime) { }
- Keep the body of the
Update
method empty for now - Under the
Update
method, define a new method namedDraw
with avoid
return type, aGameTime
parameter, and aSpriteBatch
parameter:public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
- In the body of the
Draw
method, use thespriteBatch
parameter to draw thespriteImage
with thePositionRectangle
and a white color mask:spriteBatch.Draw(spriteImage, PositionRectangle, Color.White);
That's it! The Player
class can now update and draw a player.
Player Code
At this point, the code in the Player.cs file should look like this:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace ArcadeFlyer2D
{
class Player
{
private ArcadeFlyerGame root;
private Vector2 position;
private Texture2D spriteImage;
private float spriteWidth;
public float SpriteHeight
{
get
{
float scale = spriteWidth / spriteImage.Width;
return spriteImage.Height * scale;
}
}
public Rectangle PositionRectangle
{
get
{
return new Rectangle((int)position.X, (int)position.Y, (int)spriteWidth, (int)SpriteHeight);
}
}
public Player(ArcadeFlyerGame root, Vector2 position)
{
this.root = root;
this.spriteWidth = 128.0f;
this.position = position;
LoadContent();
}
public void LoadContent()
{
this.spriteImage = root.Content.Load<Texture2D>("MainChar");
}
public void Update(GameTime gameTime)
{
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
spriteBatch.Draw(spriteImage, PositionRectangle, Color.White);
}
}
}
Using the Player
Class
Now that the Player
class has been defined, it's time to start using it in the ArcadeFlyerGame.cs file!
- Remove the
Texture2D playerImage
field from theArcadeFlyerGame
class, and add aPlayer
field:private Player player;
- At the end of the
ArcadeFlyerGame
constructor, initialize theplayer
field. Set it to a newPlayer
object, passing inthis
for the root, and a(0, 0)
vector for the position:player = new Player(this, new Vector2(0.0f, 0.0f));
- In the
LoadContent
method, remove the statement that loads the"MainChar"
image. This happens in thePlayer
class now, so it is not necessary here - In the
ArcadeFlyerGame
classUpdate
method, call thePlayer
classUpdate
method:player.Update(gameTime);
- In the
ArcadeFlyerGame
classDraw
method, remove the current drawing code betweenspriteBatch.Begin();
andspriteBatch.End();
- Replace the previous drawing code with a call to the
Player
classDraw
method, passing in thegameTime
andspriteBatch
:player.Draw(gameTime, spriteBatch);
Run the game to verify that the player sprite appears on the screen! It may seem like this was a lot of work for very little payoff, but creating the Player
class will help tremendously with the organization of the code as development continues.
Handling Input
Currently, the Update
method on the Player
class does nothing. It's time fix that and make the Player
object controllable!
Use the KeyboardState
structure to make this happen. This allows developers to detect which keys are pressed. For more information, check out this page. Make the changes in the Player.cs file.
- Add a new
float
property to thePlayer
class namedmovementSpeed
, and set it equal to4.0f
:private float movementSpeed = 4.0f;
- Define a new method on the
Player
class. The method should have:- An access modifier of
private
- A return type of
void
- A name of
HandleInput
- A parameter named
currentKeyboardState
of typeKeyboardState
private void HandleInput(KeyboardState currentKeyboardState) { }
- An access modifier of
- In the body of the
HandleInput
method, use thecurrentKeyboardState.IsKeyDown
method to check if theKeys.Up
key is pressed. Store the result in abool
variable:bool upKeyPressed = currentKeyboardState.IsKeyDown(Keys.Up);
- Create an
if
statement withupKeyPressed
as the condition:if (upKeyPressed) { }
- In the body of the
if
statement, since the up key is pressed, decrement theY
value of the position to make the player move up:position.Y -= movementSpeed;
- Repeat the steps above for the Down, Left, and Right keys. Make sure the player moves in the proper direction for each key press
- In the body of the
Update
method, useKeyboard.GetState()
to get the currentKeyboardState
for the game and store it in a variable:KeyboardState currentKeyboardState = Keyboard.GetState();
- Under that line, call the
HandleInput
method, passing in thecurrentKeyboardState
variable:HandleInput(currentKeyboardState);
The HandleInput
Method
The code for the HandleInput
method should look something like this:
private void HandleInput(KeyboardState currentKeyboardState)
{
bool upKeyPressed = currentKeyboardState.IsKeyDown(Keys.Up);
bool downKeyPressed = currentKeyboardState.IsKeyDown(Keys.Down);
bool leftKeyPressed = currentKeyboardState.IsKeyDown(Keys.Left);
bool rightKeyPressed = currentKeyboardState.IsKeyDown(Keys.Right);
if (upKeyPressed)
{
position.Y -= movementSpeed;
}
if (downKeyPressed)
{
position.Y += movementSpeed;
}
if (leftKeyPressed)
{
position.X -= movementSpeed;
}
if (rightKeyPressed)
{
position.X += movementSpeed;
}
}
The Update
method should look something like this:
public void Update(GameTime gameTime)
{
KeyboardState currentKeyboardState = Keyboard.GetState();
HandleInput(currentKeyboardState);
}
Run the program and test out the game! The main character should move based on they keyboard state!
Final Code
The final code for this walkthrough is available on GitHub.