Part 4 - Player Projectiles
In this walkthrough, add the ability for the player to fire a projectile. From an architectural perspective, there will be a Projectile
class that inherits from the Sprite
class. The ArcadeFlyerGame
class will keep track of a List
of Projectile
objects. It will create, update, and draw them as necessary. The Player
class will be able to fire a projectile by communicating with the ArcadeFlyerGame
class.
Loading a Projectile Image Asset
To create projectiles, it will be necessary to have an image for the them! Use the following fireball image, or any other image:
- Save a new image named PlayerFire.png in the "Content" folder
- Open up the Content.mgcb file in the MonoGame Pipeline Tool
- Click the "Add Existing Item" button
- Select the PlayerFire.png file
- Click the "Build" button
Now the "PlayerFire" asset should be loadable in the game!
Creating the Projectile
Class
The next thing to do is create a class
that represents a projectile.
Setup
Start with the basic setup for the class.
- Create a new file named Projectile.cs in the "src" folder
- Add
using
statements forMicrosoft.Xna.Framework
andMicrosoft.Xna.Framework.Graphics
- Create a
namespace
wrapper forArcadeFlyer2D
- In the body of the
ArcadeFlyer2D
namespace, define aclass
namedProjectile
- Use a colon (
:
) to make theProjectile
class inherit from theSprite
class- This will provide a lot of the necessary functionality for projectiles out of the box!
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace ArcadeFlyer2D
{
class Projectile : Sprite
{
}
}
Velocity
The projectile will be moving through space, so it will need a velocity. In the body of the Projectile
class, create a private Vector2
field for this purpose:
private Vector2 velocity;
Constructor
The constructor will be used to create Projectile
objects.
- In the body of the
Projectile
class, create a public constructor - Add a
Vector2
parameter:position
- Add another
Vector2
parameter:velocity
- Add a
Texture2D
parameter:spriteImage
- Use
: base()
after the method signature to call the baseSprite
constructor, passing in theposition
parameter value - In the body of the constructor, set the
velocity
field to thevelocity
parameter value - Under that, set the
SpriteWidth
property from theSprite
class to32.0f
- Under that, set the
SpriteImage
property from theSprite
class to thespriteImage
parameter value
public Projectile(Vector2 position, Vector2 velocity, Texture2D spriteImage) : base(position)
{
this.velocity = velocity;
this.SpriteWidth = 32.0f;
this.SpriteImage = spriteImage;
}
An Update
method
There should be a way for the Projectile
objects to update from one frame to the next.
- In the body of the
Projectile
class, define a new public method namedUpdate
- The method should have no parameters, and a return type of
void
- The method should have no parameters, and a return type of
- In the body of the
Update
method, increment theposition
value by thevelocity
public void Update()
{
position += velocity;
}
Projectile
Code
At the end of this section, the code in the Projectile.cs file should look something like this:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace ArcadeFlyer2D
{
class Projectile : Sprite
{
private Vector2 velocity;
public Projectile(Vector2 position, Vector2 velocity, Texture2D spriteImage) : base(position)
{
this.velocity = velocity;
this.SpriteWidth = 32.0f;
this.SpriteImage = spriteImage;
}
public void Update()
{
position += velocity;
}
}
}
Tracking Projectiles in the Game
Now that Projectile
objects can be represented in the game, it's time to use them! Open up the ArcadeFlyerGame.cs file, and introduce the idea of projectiles.
Fields
In order to track projectiles, the ArcadeFlyerGame
class will need some new fields.
- At the top of the ArcadeFlyerGame.cs file, add a
using
statement forSystem.Collections.Generic
- This will allow the class to utilize the
List
structure
- This will allow the class to utilize the
- In the body of the
ArcadeFlyerGame
class, add a new private field of typeList<Projectile>
namedprojectiles
- Under the
projectiles
field, add a new privateTexture2D
field namedplayerProjectileSprite
- This will store the image for the projectile
Initialization and Loading
Now that the fields exist, it's time to initialize them.
- At the bottom of the
ArcadeFlyerGame
constructor, set theprojectiles
field to a new empty list:projectiles = new List<Projectile>();
- In the
LoadContent
method, useContent.Load<Texture2D>
to load the "PlayerFire" image asset - Store the "PlayerFire" image in the
playerProjectileSprite
field:playerProjectileSprite = Content.Load<Texture2D>("PlayerFire");
Updating and Drawing
Each Projectile
object in the game will need to be updated and drawn each frame.
- In the body of the
Update
method, create a newforeach
loop - In the loop, cycle through each
Projectile
object in theprojectiles
list - Call the
Projectile
'sUpdate
method on eachProjectile
object:foreach (Projectile p in projectiles) { p.Update(); }
- In the body of the
Draw
method, between thespriteBatch.Begin
andspriteBatch.End
, create a newforeach
loop - In the loop, cycle through each
Projectile
object in theprojectiles
list - Call the
Sprite
'sDraw
method on eachProjectile
object, passing in thegameTime
andspriteBatch
:foreach (Projectile p in projectiles) { p.Draw(gameTime, spriteBatch); }
Firing
The ArcadeFlyerGame
class should have the ability to fire a projectile. This will involve creating a new Projectile
object, and adding it to the projectiles
list.
- In the body of the
ArcadeFlyerGame
class, define a new public method namedFireProjectile
- Make the return type of the
FireProjectile
methodvoid
- Give the
FireProjectile
twoVector2
parameters:position
andvelocity
- In the body of the
FireProjectile
method, define aProjectile
object namedfireProjectile
- Set the
fireProjectile
variable to be a newProjectile
object- Pass in the
position
,velocity
, andplayerProjectileSprite
to theProjectile
constructor
- Pass in the
- Add the newly created
fireProjectile
to theprojectiles
list
public void FireProjectile(Vector2 position, Vector2 velocity)
{
Projectile firedProjectile = new Projectile(position, velocity, playerProjectileSprite);
projectiles.Add(firedProjectile);
}
Firing Projectiles from the Player
Class
Now that game can properly handle Projectile
objects, allow the Player
to fire them! Open up the Player.cs file to get started. The Player
should fire a projectile when the user presses the Space key.
- At the bottom of the
HandleInput
method, create anif
statement - For the condition of the
if
statement, check if the Space key is pressed - In the body of the
if
statement, create a newVector2
variable namedprojectilePosition
- This will be where the projectile is created on the screen
- For the
X
value of the newVector2
, set it to the currentX
position of the sprite, plus the total width of the sprite- This will make the projectile appear on the right side of the player sprite
- For the
Y
value of the newVector2
, set it to the currentY
position of the sprite, plus the sprite's height divided by2
- This will make the projectile appear at the vertical middle of the player sprite
- Under that, create another new
Vector2
variable namedprojectileVelocity
- Set the
X
value for theprojectileVelocity
to10.0f
- This will make the projectile move to the right
- Set the
Y
value for theprojectileVelocity
to0.0f
- Under that, use
root.FireProjectile
to fire the projectile withprojectilePosition
andprojectileVelocity
The code should look something like this:
if (currentKeyboardState.IsKeyDown(Keys.Space))
{
Vector2 projectilePosition = new Vector2(position.X + SpriteWidth, position.Y + SpriteHeight / 2);
Vector2 projectileVelocity = new Vector2(10.0f, 0.0f);
root.FireProjectile(projectilePosition, projectileVelocity);
}
Run the program and see how it works. The player can fire projectiles, which is great! The only problem is that there is no limit to the number of projectiles that can fire.
Timed Firing
To make the game a little more interesting, only allow the player to fire projectiles at a certain rate. This is possible using a timer mechanism.
Fields
There will be a few necessary fields to keep track of the cool down period for the projectile firing. Add the following private fields to the Player
class:
- A
float
namedprojectileCoolDownTime
set to0.5f
- This is the total amount of time (in seconds) the timer will last
- A
float
namedprojectileTimer
set to0.0f
- This is the current time (in seconds) for the timer
- A
bool
namedprojectileTimerActive
set tofalse
- This keeps track of whether or not the player is currently cooling down
private float projectileCoolDownTime = 0.5f;
private float projectileTimer = 0.0f;
private bool projectileTimerActive = false;
Conditional Firing
Next, update the code so that a projectile can only fire if the player is not cooling down.
- In the body of the
HandleInput
method, find theif
statement checking if Space is pressed - Update the condition so that it checks for BOTH the key press, AND whether the projectile timer is not currently active:
if (!projectileTimerActive && currentKeyboardState.IsKeyDown(Keys.Space))
- In the body of the
if
statement, at the bottom, set theprojectileTimerActive
field totrue
- This will kick off a new cool down process
- Under that, set the
projectileTimer
field to0.0f
- This will reset the timer
Timer Updates
For this to work, the timer will have to be updated so that it properly counts down the time! This will require the use of the MonoGame GameTime
structure. The GameTime
structure allows developers to track how much time has passed from frame to frame. This is useful for anything in the game that involves time. Check out this article for more information.
- In the body of the
Update
method, at the bottom, create a newif
statement - For the
if
condition, check ifprojectileTimerActive
is true- This means the timer must be updated
- In the body of the
if
statement, increment the value of theprojectileTimer
field based on the total seconds fromgameTime
- Under that, create another
if
statement - In the new
if
statement, check if theprojectileTimer
has reached the totalprojectileCoolDownTime
threshold- This means the time is up
- In the body of the new
if
statement, setprojectileTimerActive
tofalse
because the timer has completed
if (projectileTimerActive)
{
projectileTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;
if (projectileTimer >= projectileCoolDownTime)
{
projectileTimerActive = false;
}
}
Run the code again, and this time the player should only be able to fire a new projectile every half-second!
Final Code
The final code for this walkthrough is available on GitHub.