Using Managed DirectX to Write a Game

Implementing the Finishing Touches

Currently, you don't do anything with these state variables. You should implement the game-over logic first. You will force the player to hit any key to start the game. After the game is over (you've crashed), there will be a slight pause (approximately one second), and then hitting any key will start the game once again. The first thing you'll need to make sure is that while the game is over, you don't update any other state. So the very first lines in OnFrameUpdate should be

// Nothing to update if the game is over
if ((isGameOver) || (!hasGameStarted))
  return;

Now you need to handle the keystrokes to restart the game. At the very end of your OnKeyDown override, you can add this logic:

if (isGameOver)
{
  LoadDefaultGameOptions();
}
// Always set isGameOver to false when a key is pressed
isGameOver = false;
hasGameStarted = true;

This is the behavior you want. Now, when the game is over, and the player presses a key, a new game is started with the default game options. You can remove the call to LoadDefaultGameOptions from InitializeGraphics now if you want to, since it will be called as soon as you press a key to start a new game. However, you still don't have the code that will cause the slight pause after you crash. You can add that to your OnKeyDown override as well; you should do it directly after checking for Escape:

// Ignore keystrokes for a second after the game is over
if ((System.Environment.TickCount - gameOverTick) < 1000)
{
  return;
}

This will ignore any keystrokes (except Escape to quit) for a period of one second after the game is over. Now, you can actually play the game! There's definitely something missing, though. You're maintaining a score, but there is nothing in the game that tells the user anything. You will need to rectify that. There is a Font class in the Direct3D namespace that can be used to draw some text; however, there is also a Font class in the System.Drawing namespace, and these two class names will collide if you attempt to use "Font" without fully qualifying it. Luckily, you can alias with the using statement as follows:

using Direct3D = Microsoft.DirectX.Direct3D;

Now each font you create can be a different color, but must be a unique size and family. You will want to draw two different types of text for this game, so you will need two different fonts. Add the following variables to the DodgerGame class:

// Fonts
private Direct3D.Font scoreFont = null;
private Direct3D.Font gameFont = null;

You will need to initialize these variables, but can't do so until the device has been created. You should do this after the device creation code. This does not need to happen in the OnDeviceReset event since these objects will automatically handle the device reset for you. Just add these lines to the end of the InitializeGraphics method:

// Create our fonts
scoreFont = new Direct3D.Font(device, new System.Drawing.Font("Arial", 12.0f,
  FontStyle.Bold));
gameFont = new Direct3D.Font(device, new System.Drawing.Font("Arial", 36.0f,
  FontStyle.Bold | FontStyle.Italic));

You've created two fonts of different sizes, but of the same family: Arial. Now you need to update the rendering method to draw the text. You will want the text to be drawn last, so after the car's draw method, add the following:

if (hasGameStarted)
{
  // Draw our score
  scoreFont.DrawText(null, string.Format("Current score: {0}", score),
    new Rectangle(5,5,0,0), DrawTextFormat.NoClip, Color.Yellow);
}
if (isGameOver)
{
  // If the game is over, notify the player
  if (hasGameStarted)
  {
    gameFont.DrawText(null, "You crashed. The game is over.", new Rectangle(25,45,0,0),
      DrawTextFormat.NoClip, Color.Red);
  }
  if ((System.Environment.TickCount - gameOverTick) >= 1000)
  {
    // Only draw this if the game has been over more than one second
    gameFont.DrawText(null, "Press any key to begin.", new Rectangle(25,100,0,0),
      DrawTextFormat.NoClip, Color.WhiteSmoke);
  }
}

The DrawText method will be discussed in depth in a later chapter, so for now, just know that it does what its name implies. It draws text. As you can see here, once the game has been started the first time, you will always show the current score. Then, if the game is over, and it has been started for the first time already, you notify the player that they have crashed. Finally, if the game has been over for a second or longer, let the player know that pressing a key will start a new game.

Wow! Now you've got a full-fledged game in the works. You can play, see your score increase, see your game end when you crash, and start over and play again. What's left? Well, it would be awesome if you could store the highest scores we've achieved so far.

Adding High Scores

First off, we will need a place to store the information for the high scores. We will only really care about the name of the player as well as the score they achieved, so we can create a simple structure for this. Add this into your main games namespace:

/// <summary>
/// Structure used to maintain high scores
/// </summary>
public struct HighScore
{
  private int realScore;
  private string playerName;
  public int Score { get { return realScore; } set { realScore = value; } }
  public string Name { get { return playerName; } set { playerName = value; } }
}

Now we will also need to maintain the list of high scores in our game engine. We will only maintain the top three scores, so we can use an array to store these. Add the following declarations to our game engine:

// High score information
private HighScore[] highScores = new HighScore[3];
private string defaultHighScoreName = string.Empty;

All we need now is three separate functions. The first will check the current score to see if it qualifies for inclusion into the high score list. The next one will save the high score information into the registry, while the last one will load it back from the registry. Add these methods to the game engine:

/// <summary>
/// Check to see what the best high score is. If this beats it,
/// store the index, and ask for a name
/// </summary>
private void CheckHighScore()
{
  int index = -1;
  for (int i = highScores.Length - 1; i >= 0; i--)
  {
    if (score >= highScores[i].Score) // We beat this score
    {
      index = i;
    }
  }
  // We beat the score if index is greater than 0
  if (index >= 0)
  {
    for (int i = highScores.Length - 1; i > index ; i--)
    {
      // Move each existing score down one
      highScores[i] = highScores[i-1];
    }
    highScores[index].Score = score;
    highScores[index].Name = Input.InputBox("You got a high score!!",
      "Please enter your name.", defaultHighScoreName);
  }
}
/// <summary>
/// Load the high scores from the registry
/// </summary>
private void LoadHighScores()
{
  Microsoft.Win32.RegistryKey key =
    Microsoft.Win32.Registry.LocalMachine.CreateSubKey(
    "Software\\MDXBoox\\Dodger");
  try
  {
    for(int i = 0; i < highScores.Length; i++)
    {
      highScores[i].Name = (string)key.GetValue(
        string.Format("Player{0}", i), string.Empty);
      highScores[i].Score = (int)key.GetValue(
        string.Format("Score{0}", i), 0);
    }
    defaultHighScoreName = (string)key.GetValue(
      "PlayerName", System.Environment.UserName);
  }
  finally
  {
    if (key != null)
    {
      key.Close(); // Make sure to close the key
    }
  }
}
/// <summary>
/// Save all the high score information to the registry
/// </summary>
public void SaveHighScores()
{
  Microsoft.Win32.RegistryKey key =
    Microsoft.Win32.Registry.LocalMachine.CreateSubKey(
    "Software\\MDXBoox\\Dodger");
  try
  {
    for(int i = 0; i < highScores.Length; i++)
    {
      key.SetValue(string.Format("Player{0}", i), highScores[i].Name);
      key.SetValue(string.Format("Score{0}", i), highScores[i].Score);
    }
    key.SetValue("PlayerName", defaultHighScoreName);
  }
  finally
  {
    if (key != null)
    {
      key.Close(); // Make sure to close the key
    }
  }
}

I won't delve too much into these functions since they deal mainly with built-in .NET classes and have really nothing to do with the Managed DirectX code. However, it is important to show where these methods get called from our game engine.

The check for the high scores should happen as soon as the game is over. Replace the code in OnFrameUpdate that checks if the car hits an obstacle with the following:

if (o.IsHittingCar(car.Location, car.Diameter))
{
  // If it does hit the car, the game is over.
  isGameOver = true;
  gameOverTick = System.Environment.TickCount;
  // Stop our timer
  Utility.Timer(DirectXTimer.Stop);
  // Check to see if we want to add this to our high scores list
  CheckHighScore();
}

You can load the high scores at the end of the constructor for the main game engine. You might notice that the save method is public (while the others were private). This is because we will call this method in our main method. Replace the main method with the following code:

using (DodgerGame frm = new DodgerGame())
{
  // Show our form and initialize our graphics engine
  frm.Show();
  frm.InitializeGraphics();
  Application.Run(frm);
  // Make sure to save the high scores
  frm.SaveHighScores();
}

The last thing we need to do is actually show the player the list of high scores. We will add this into our rendering method. Right before we call the end method on our game font, add this section of code to render our high scores:

// Draw the high scores
gameFont.DrawText(null, "High Scores: ", new Rectangle(25,155,0,0),
  DrawTextFormat.NoClip, Color.CornflowerBlue);
for (int i = 0; i < highScores.Length; i++)
{
  gameFont.DrawText(null, string.Format("Player: {0} : {1}", highScores[i].Name,
    highScores[i].Score),
    new Rectangle(25,210 + (i * 55),0,0), DrawTextFormat.NoClip,
        Color.CornflowerBlue);
}

Take a moment to pat yourself on the back. You've just finished your first game.

In Brief

  • Use the mesh classes to render the game objects.

  • Check certain device capabilities.

  • Basic user input.

  • Design a scoring system.

  • Putting it all together.

See if you can beat my best score in Figure 6.1 .

Figure 6.1
The completed game.

This is a sample chapter from Managed DirectX 9 Kick Start : Graphics and Game Programming

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Measuring programming progress by lines of code is like measuring aircraft building progress by weight.” - Bill Gates