Chapter 11: Adding HUD Text, Splash Screens, and
Special Effects
Our game is getting close to being complete. But there are still a few elements that are important and need to go in the game. The first is adding some text to our display. We'll add a little point counter and live counter. We'll also need a simple menu system, which for now will be just a few splash screens. Lastly we want to add in some special effects, and we'll add an explosion effect using particle systems. The goals for this chapter are:
l Learn how to draw text on a HUD (Heads Up Display.)
l Add some simple splash screens to the game.
l Learn what particle systems are and use them to create explosion effects.
Adding HUD Text
What we mean by adding HUD text is pretty straightforward, we are just going to go over how to add some text displayed at various parts of the screen. The way we'll do this is to create an object called a Sprite Font. This object reads in a font from our system and uses it to create text in our game (which means you have to be careful about legal issues, as fonts on your system are copyrighted and may not be distributable. Check at Microsoft's XNA's sight for more details.)
To add text to the screen we'll do the following (which is adapted from the XNA documentation):
Right-click on the Content folder in Solution Explorer, click Add, and then click New Item. Choose Sprite Font from the Add New Item dialog box. Name the Sprite Font ShipFont, since it will be the only font we're using for the game. XNA creates the font and double click it to open it. You'll see an xml file with several fields:
<!--
Modify this string to change the font that will be imported.
-->
<FontName>SpriteFont1</FontName>
<!--
Size is a float value, measured in points. Modify this value to change
the size of the font.
-->
<Size>14</Size>
<!--
Spacing is a float value, measured in pixels. Modify this value to change
the amount of spacing in between characters.
-->
<Spacing>2</Spacing>
<!--
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
and "Bold, Italic", and are case sensitive.
-->
<Style>Regular</Style>
This is the file XNA parses to create text for the screen. In the FontName element where it says SpriteFont1 delete that and put in Courier New. This is the font we'll be using for the ship game, but you can put the name of any font you have on your machine. Then they are a few other elements to describe the text you can change, such as the size, spacing, and if it should be bold or italic. We'll leave these to their defaults, but play around with them to see different text effects.
Next let's add the sprite to our game. But before that we'll add two more variables, one in the player class and one in the game class for the information we want to display. For the player we'll keep track of lives. We'll just create a variable called lives, set it to a default number (5) and every time the player is hit by something we'll subtract one from it. So int the Player.cs add the following:
// Total number of lives
int lives = 5;
public int Lives
{
set { lives = value; }
get { return lives; }
}
We put in the Lives property so we can access the number of lives from outside the player class. Next we'll change the CollisionTest method to the following:
public bool CollisionTest(Vector2 point, float radius)
{
if ((point - position).Length() < this.radius + radius)
{
if (!recoveringActive)
{
lives--;
recoveringActive = true;
blinkTimeTotal = 0.0f;
}
return true;
}
return false;
}
The only difference here from the original is the addition of the line lives--; So when we are hit with something the number of lives now gets decremented once.
Next we'll keep track of the total number of points for our player. We'll have a variable called points in the Game class:
// Keep track of the points for the player
float points = 0;
And we'll add to it whenever we hit an enemy:
if (collide != -1)
{
enemyShipList.RemoveAt(i);
playerFireballList.RemoveAt(collide);
points += 200;
}
Here we just add 200 points every time an enemy is hit.
That sets us up for what we want to display for text on the screen, now to display it all. We'll keep the number of lives in the upper left corner of the screen and the total points in the upper right. So besides the Sprite Text object we'll need two position vectors. In the Game class add the following variables:
SpriteFont CourierNew;
// Location to draw the text
Vector2 textLeft;
Vector2 textMiddle;
CourierNew is the Sprite Font we created before and the other two are the text position vectors. To initialize them add the following to LoadContent():
// Create the font
CourierNew = Content.Load<SpriteFont>("ShipFont");
// Set text positions
textMiddle = new Vector2(graphics.GraphicsDevice.Viewport.Width / 1.3f,
graphics.GraphicsDevice.Viewport.Height / 30);
textLeft = new Vector2(graphics.GraphicsDevice.Viewport.Width / 30,
graphics.GraphicsDevice.Viewport.Height / 30);
This just creates the font and the two position Vectors. The text position vectors use some rough calculations based on the window width to find their location. Next we'll add the following function to the Game class:
public void DrawText(SpriteBatch TextBatch)
{
string output = "Lives: " + playerShip.Lives.ToString();
TextBatch.DrawString(CourierNew, output, textLeft, Color.White);
string pointString = points.ToString();
for (int i = pointString.Length; i < 8; i++)
pointString = "0" + pointString;
pointString = "Points: " + pointString;
TextBatch.DrawString(CourierNew, pointString, textMiddle, Color.White);
}
The main part of this is the SpriteBatch.DrawString() method. It works by calling the following:
spriteBatch.DrawString( SpriteFont, string textToPrint, Vector2 position, Text
Color);
This method is simple enough, we just tell the SpriteBatch what font we want to use for the text, then give it a string of text to draw on the screen. We also tell it a position for the text and a color for it. In our ship game we use DrawText to draw the lives and points. The output looks like:

Adding Splash Screens
Now we have some HUD text up, let's add some splash screens. We'll add a start screen and an end screen, to make the game more complete for now.
But what we're really adding is more states to our game. If you recall the discussion from early in this section we can think of things in a game based on what state they are currently in. For the Ship Fighter game we'll create three possible states the game can be in, a start state: that occurs when you start the game, a running state that is when the game is running (so far all of our games have just been in the running state) and an end state, that occurs when the player runs out of lives or runs out of time (we'll put in a timeout feature.) The start state will just draw a big sprite over the entire screen and wait for a key press. The end state will also just draw another splash screen and wait for a key press to restart.
To start add the following enum to Game1.cs:
public enum GameState
{
StartScreen,
Running,
EndScreen
}
This structure is called an enum. Enums work by creating a variable name and list of values. Then we can create a variable of the type and it can only have the values we specified. This is clearer if you look at the one we make for the game state; it helps us write code for the different states we can be in. In the game class we'll add a variable to track the current state:
GameState gameState = GameState.StartScreen;
The variable gameState can have one of three values, StartScreen, Running, or EndScreen. Let's go ahead and add the variables to hold the sprites for our splash screens and a float to track the entire time and the totalTime the game can run (endTime):
// total time tracker and endTime (timeout)
float endTime = 10.0f;
// Textures for our splash screen
Texture2D startScreen;
Texture2D endScreen;
For the two textures we'll need to load them in the LoadContent method:
startScreen = content.Load<Texture2D>("startGameSplash");
endScreen = content.Load<Texture2D>("endGameSplash");
For the different game states we'll choose between them by using ifs in the Draw and Update loop based on the current game state. For the Draw it will just be:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
if (gameState == GameState.StartScreen)
{
DrawStartScreen();
}
else if (gameState == GameState.Running)
{
spaceBackground.Draw();
spriteBatch.Begin();
foreach (Fireball f in playerFireballList)
f.Draw(spriteBatch);
playerShip.Draw(spriteBatch);
foreach (Fireball f in enemyFireballList)
f.Draw(spriteBatch);
foreach (Enemy e in enemyShipList)
e.Draw(spriteBatch);
DrawText(spriteBatch);
spriteBatch.End();
}
else if (gameState == GameState.EndScreen)
{
DrawEndScreen();
}
base.Draw(gameTime);
}
We just switch what we're doing based on the current state. If the current state is StartScreen we call
DrawStartScreen (a method we need to add). If we're in the running state we just draw as we were before. And if we're in the Endstate we draw the endscreen. The DrawStartScreen and DrawEndScreen methods are pretty simple, they just draw the appropriate sprite onscreen:
// Draw the start screen
private void DrawStartScreen()
{
spriteBatch.Begin();
spriteBatch.Draw(startScreen, Vector2.Zero, null, Color.White);
spriteBatch.End();
}
// Draw the end screen
private void DrawEndScreen()
{
spriteBatch.Begin();
spriteBatch.Draw(endScreen, Vector2.Zero, null, Color.White);
spriteBatch.End();
}
Likewise for the Update loop we'll toggle what we do based on the current state. After the time Update in the Update loop we add the following:
// If we are starting or ending just update Splash screen
if (gameState == GameState.StartScreen || gameState == GameState.EndScreen)
UpdateSplashScreen();
else // GameState is running
{
And the rest of this is just the regular Update loop. So if we are not in the running state we just call the update function as usual, but we'll add a line to check if the player lives are below zero, if they are we'll set the state to End. When the game is over we also want to reset our game to a start state. We’ll put the player back in its start position and clear out all of the enemies and fireballs. Here is the code:
if (playerShip.Lives < 0 || totalTime > endTime)
{
gameState = GameState.EndScreen;
playerShip.Position = new Vector2( 400.0f, 500.0f );
enemyFireballList.Clear();
enemyShipList.Clear();
playerFireballList.Clear();
}
For the UpdateSplashScreen method we'll see if the start button on the Xbox controller is hit or if the space bar is pressed on the keyboard. If either of those is true we just switch the current state to running.
private void UpdateSplashScreen()
{
KeyboardState keyState = Keyboard.GetState();
if (GamePad.GetState(PlayerIndex.One).Buttons.Start ==
ButtonState.Pressed || keyState.IsKeyDown(Keys.Space))
{
gameState = GameState.Running;
totalTime = 0.0f;
}
}
Now if you run the game you get

We have a pretty complete game setup so far. Once last feature is a special effect. The special effect we'll add in is a particle explosion for whenever the enemy ships are hit. Next let's take a look at particle systems.
Particle Systems
Some effects we want to model in video games are made up of many small pieces, particles, interacting. Water and smoke (and explosions) are examples of this. To create explosions we could create an animation, drawing out every frame of the animation. The problem with that approach is that each explosion should be a little bit different. Another approach is to use particle systems. Particle systems can get very complex, but the basic concept is pretty simple. You create a list of particles, each one having a start state (color), end state, and velocity. Over time you update each particle individually and this creates an effect overall. For the explosion effect in our game here is a simple source file, BasicParticleSystem.cs, that does this for explosions:
class Particle
{
Vector4 color;
Vector4 startColor;
Vector4 endColor;
TimeSpan endTime = TimeSpan.Zero;
TimeSpan lifetime;
public Vector3 position;
Vector3 velocity;
protected Vector3 acceleration = new Vector3( 1.0f, 1.0f, 1.0f );
public bool Delete;
public Vector4 Color
{
get
{
return color;
}
}
public Particle(Vector2 position2, Vector2 velocity2, Vector4 startColor,
Vector4 endColor, TimeSpan lifetime)
{
velocity = new Vector3(velocity2, 0.0f);
position = new Vector3(position2, 0.0f);
this.startColor = startColor;
this.endColor = endColor;
this.lifetime = lifetime;
}
public void Update(TimeSpan time, TimeSpan elapsedTime)
{
//Start the animation 1st time round
if (endTime == TimeSpan.Zero)
{
endTime = time + lifetime;
}
if (time > endTime)
{
Delete = true;
}
float percentLife = (float)((endTime.TotalSeconds - time.TotalSeconds)
/ lifetime.TotalSeconds);
color = Vector4.Lerp(endColor, startColor, percentLife);
velocity += Vector3.Multiply(acceleration,
(float)elapsedTime.TotalSeconds);
position += Vector3.Multiply(velocity,
(float)elapsedTime.TotalSeconds);
}
}
class BasicParticleSystem
{
static Random random = new Random();
List<Particle> particleList = new List<Particle>();
Texture2D circle;
int Count = 0;
public BasicParticleSystem(Texture2D circle)
{
this.circle = circle;
}
public void AddExplosion(Vector2 position)
{
for (int i = 0; i < 300; i++)
{
Vector2 velocity2 = (float)random.Next(100) *
Vector2.Normalize(new Vector2((float)(random.NextDouble() –
.5), (float)(random.NextDouble() - .5)));
particleList.Add(new Particle(
position,
velocity2,
(i > 70) ? new Vector4(1.0f, 0f, 0f, 1) : new Vector4(.941f,
.845f, 0f, 1),
new Vector4(.2f, .2f, .2f, 0f),
new TimeSpan(0, 0, 0, 0, random.Next(1000) + 500)));
Count++;
}
}
public void Update(TimeSpan time, TimeSpan elapsed)
{
if (Count > 0)
{
for( int i = 0; i < particleList.Count; i++ )
{
particleList[i].Update(time, elapsed);
if (particleList[i].Delete) particleList.RemoveAt(i);
}
Count = particleList.Count;
}
}
public void Draw(SpriteBatch batch)
{
if (Count != 0)
{
int particleCount = 0;
foreach (Particle particle in particleList)
{
batch.Draw(circle,
new Vector2(particle.position.X, particle.position.Y),
null, new Color(((Particle)particle).Color), 0,
new Vector2(16, 16), .2f,
SpriteEffects.None, particle.position.Z);
particleCount++;
}
}
}
}
Again, we won’t step through this code but looking at it quickly you can see that it has a particle class and a BasicParticleSystem class that let’s us add an explosion and draw/update it. If you add this file to the ShipFighter project then adding it to the Game class is pretty straightforward. (Also add in the “circle.tga” art file to the project, as we’ll use this for the picture for our particles.) First in the Game class add the following variable:
BasicParticleSystem particleSystem;
TimeSpan totalTimeSpan = new TimeSpan();
This declares our particle system. We are also going to be using a time variable to track the overall time for updating our system, so we added the totalTimeSpan structure. The next step is to initialize the particle system. In the LoadContent method of the game class add in:
particleSystem = new BasicParticleSystem(Content.Load<Texture2D>("circle"));
This creates our particle system. Then we need to draw and update it. In the draw loop where we are using our spaceSpriteBatch add the following:
particleSystem.Draw(spriteBatch);
And in the update loop add:
totalTimeSpan += gameTime.ElapsedGameTime;
particleSystem.Update(totalTimeSpan, gameTime.ElapsedGameTime);
This updates our time for the system and the particle system itself. The last thing to do is to add an explosion to the system whenever an enemy is hit by a fireball or crashes into the player. Here is a modification of the collision detection part of the update loop that does this:
int collide = enemyShipList[i].CollisionBall(playerFireballList);
if (collide != -1)
{
particleSystem.AddExplosion(enemyShipList[i].Position);
enemyShipList.RemoveAt(i);
playerFireballList.RemoveAt(collide);
points += 200;
}
else
if (playerShip.CollisionTest(enemyShipList[i].Position, enemyShipList[i].Radius))
{
particleSystem.AddExplosion(enemyShipList[i].Position);
enemyShipList.RemoveAt(i);
}
else if (enemyShipList[i].Position.Y > 2000.0f)
enemyShipList.RemoveAt(i);
This sets up our explosions. If you run the game now you’ll see them whenever you hit an enemy:

End
This brings us to the end of the game Ship Fighter. We’ve only scratched the surface of game programming. I suggest you look at the Further Resources section after this to find out more about game programming.
