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.

SHOP TALK: 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:

[View full width]

/// <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 graphics/ccc.gif = value; } } public string Name { get { return playerName; } set { graphics/ccc.gif 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:

[View full width]

/// <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 graphics/ccc.gif 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), graphics/ccc.gif highScores[i].Name); key.SetValue(string.Format("Score{0}", i), graphics/ccc.gif 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:

[View full width]

// 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}", graphics/ccc.gif 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.



Managed DirectX 9 Graphics and Game Programming, Kick Start
Managed DirectX 9 Kick Start: Graphics and Game Programming
ISBN: B003D7JUW6
EAN: N/A
Year: 2002
Pages: 180
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net