Game screens
Racing games have many different game screens, which are managed by the gamescreens stack in the racinggame class. This section describes most of the screens and functions used in the game. Most game screens are quite simple, but others are a bit complicated and implement a unit test, which gives you a better understanding of this class. For example, the credits screen is quite simple. It only displays one background texture, but the main menu is complicated and has all the buttons that can enter another screen. The options screen introduces many new control options, all of which need to be tested. A unit test in this class can help you complete this process.
All game screen classes inherit from the igamescreen interface (see Figure 14-6). You may remember to use them in the rocket Commander in Chapter 8th. Almost unchanged, but easier. You only have one method called render. It does not have a parameter and returns a Boolean value. TheCodeIt is almost the same as what you use in xNa shooter. If the render method returns true, you can return to the previous game screen. Once the last game screen is released, the game is released. Generally, false is returned for the game screen because the player does not exit immediately after entering the game screen again.
Figure 14-6
Because of this simple interface, the class views of all game screens are almost the same. They all have only one render method. Some classes also have some private auxiliary methods, but they are mostly very simple. Some more complex game screens also have unit tests to test whether all content and functions can be correctly implemented. Processing the entire game's gamescreen class is complicated. Unlike xNa shooeter or rocket Commander, the processing and rendering of all game code are in the landscape and model classes. The game logic is processed in the player class. This is a bit complicated, but you have learned all the basic physical knowledge of the carphysics class and the chasecamera class in the previous chapter.
Let's take a look at figure 14-7 to get a basic overview of the game screen. The most complex class is obviously the Mission class, which handles the game process and game logic. It does not contain all game code, and some are processed in the player and racinggame classes.
Figure 14-7
Enable screen
The startup screen (see Figure 14-8) is an easy class, which is just waiting for the player to press start on the handle. If there is no handle, the space, ESC key, or left mouse button can also enable the player to continue. The only interesting part of this class is the code that flashes the "press start to continue" text.
Figure 14-8
/// <Summary> /// render splash screen /// </Summary> Public bool render () {// This starts both menu and in game post screen Shader! Basegame. UI. postscreenmenushader. start (); // render background and black bar basegame. UI. rendermenubackground (); basegame. UI. renderblackbar (352, 61); // show press start to continue. if (INT) (basegame. totaltime/0.375f) % 3! = 0) basegame. UI. logos. renderonscreen (basegame. calcrectanglecenteredwithgivenheight (512,352 + 61/2, 26, uirenderer. pressstartgfxrect), uirenderer. pressstartgfxrect); // show logos basegame. UI. renderlogos (); // clicking or pressing start will go to the menu return input. mouseleftbuttonjustpressed | input. keyboardspacejustpressed | input. keyboardescapejustpressed | input. gamepadstartpressed;} // render ()
The rendermenubackground method is a bit complicated. It is used to display the menu background, that is, displaying a car passing through a track. A car is controlled by a computer, and a camera just follows it. The code is not very complex:
// [From rendermenutrackbackground (), which is called by // rendermenubackground (), both located in the uirenderer class] // [some code to calculate carpos, carmatrix, etc.] // put camera behind carracinggame. player. setcameraposition (carpos + carmatrix. forward * 9-carmatrix. up * 2.3f); // for rendering rotate car to stay correctly on the roadcarmatrix = matrix. createrotationx (mathhelper. PI/2.0f) * matrix. createrotationz (mathhelper. pi) * carmatrix; racinggame. landscape. render (); racinggame. carmodel. rendercar (randomcarnumber, randomcarcolor, carmatrix );
With the render method of the landscape class and the rendercar method of the model class, you do not have to worry about rendering scenes, tracks, or other things. The camera matrix ensures that you are watching in the right position, while the racing car is driving on the track through the car matrix ..
All other game screen menus use the rendermenubackground method to display the screen background, but it is not obvious on some game screens because you put a darker texture in front (for example, it is difficult to see the background on the credits screen ). This is just a background effect. When you start playing games, you can see more screens.
Main Menu
The main menu (see Figure 14-9) is more complex than other menu screens, but this class only has about 250 lines of code. Apart from starting the screen, other screens start from here. The most important option is to start the game and watch highscores (the first two buttons ).
Figure 14-9
The coolest feature of this class is menu button animation. Each button gets a floating point value between 0 and 1, where 0 represents the minimum possible value of the button size, and 1 represents the maximum. When you hover the mouse or use a handle or keyboard to select a button, it will gradually grow until it reaches 1.0. When you leave the button, it slows down.
The first button is initially set to 1, and the other buttons are set to the minimum size (0 ).
/// <Summary> /// current button sizes for scaling up/down smooth effect. /// </Summary> float [] currentbuttonsizes = new float [numberofbuttons] {1, 0, 0, 0, 0 };
Then select and scale the buttons in the rnnder method. To ensure that you are hovering over one or more buttons at the same time, an auxiliary variable is used for the mouse. If you do not use the mouse to select the menu button, this variable will not be used.
// Little helper to keep track if mouse is actually over a button. // required because buttons are selected even when not hovering over // them for gamepad support, but we still want the mouse only to // be applied when we are actually over the button.int mouseisoverbutton =-1; // [a little later in the code...] for (INT num = 0; num <numberofbuttons; num ++) {// is this button currently selected? Bool selected = num = selectedbutton; // increase size if selected, decrease otherwise currentbuttonsizes [num] + = (selected? 1:-1) * basegame. movefactorpersecond * 2; If (currentbuttonsizes [num] <0) currentbuttonsizes [num] = 0; If (currentbuttonsizes [num]> 1) currentbuttonsizes [num] = 1; // use this size to build rect rectangle thisrect = interpolaterect (activerect, inactiverect, currentbuttonsizes [num]); rectangle renderrect = new rectangle (xpos, ypos-(thisrect. height-inactiverect. height)/2, thisrect. width, Thisrect. Height); basegame. UI. Buttons. renderonscreen (renderrect, buttonrects [num], // make button gray if not selected? Color. white: new color (192,192,192,192); // Add border effect if selected if (selected) basegame. UI. buttons. renderonscreen (renderrect, uirenderer. menubuttonselectiongfxrect); // also check if the user hovers with the mouse over this button if (input. mouseinbox (renderrect) mouseisoverbutton = num; // [etc.]} // For (Num) if (mouseisoverbutton> = 0) selectedbutton = mouseisoverbutton;
Game Screen
The gamescreen class (see Figure 14-10) is the most important game screen because it handles the entire game logic. Game variables are not stored in this class, but all important components (scenes, tracks, cars, objects, Hud, etc.) are rendered and called from here.
Figure 14-10
Most player variables are stored in the player class, And all input and Physical Operations are processed in the player base class (carphysics and chasecamera. The player class also uses all game variable competitions. Most variables are displayed on the user interface. For example, during the current game time on the HUD, they are updated in the handlegamelogic method of the player class.
All render methods, including HUD, are processed in the uirenderer helper class, the rest are processed and rendered in the landscape class and model class, and the shadow ing is also performed there. All rendering and game processing are called from here, so this class provides you with a good summary of what happened in the game. If you write an adapted version, modify the code from here. Comment out the code in this or call method to quickly see which part of the game is affected.
The first part of the render method processes all the shadow mappings, which is not very complicated because most of them are processed in the landscape class. You only need to provide the data to the Shadow ing Class, which renders all shadow mappings that can be directly used.
/// <Summary> /// render game screen. called each frame. /// </Summary> Public bool render () {If (basegame. allowshadowmapping) {// generate shadows shadereffect. shadowmapping. generateshadows (delegate {racinggame. landscape. generateshadow (); racinggame. carmodel. generateshadow (racinggame. player. carrendermatrix) ;}); // render shadows shadereffect. shadowmapping. rendershadows (delegate {racinggame. landscape. useshadow (); racinggame. carmodel. useshadow (racinggame. player. carrendermatrix) ;};}// if (basegame. allowshadowmapping)
Then start post-screen glow shader and render all 3D content. This includes the sky box, scenes with tracks and all 3D models, and finally cars.
// This starts both menu and in game post screen Shader! Basegame. UI. postscreenglowshader. start (); // render background sky and lensflare. basegame. UI. rendergamebackground (); // render landscape with track and all objectsracinggame. landscape. render (); // render car with matrix we got from carphysicsracinggame. carmodel. rendercar (racinggame. currentcarnumber, racinggame. carcolor, racinggame. player. carrendermatrix); // and flush all models to be renderedbasegame. meshrendermanager. render ();
After meshrendermanager renders all 3D models, you can add a shadow ing effect. The call sequence here is important, because if the 3D model has not been rendered before the shadow is displayed, the shadow will be incorrect or will not work.
// Show shadows we calculated aboveif (basegame. allowshadowmapping) {shadereffect. shadowmapping. showshadows ();} // If (basegame. allowshadowmapping) // apply post screen shader here before doing the uibasegame. UI. postscreenglowshader. show ();
The final part of the code is the game user interface. If you want to remove or change the HUD, do it here.
// Play motor soundsound. updategearsound (racinggame. player. speed, racinggame. player. acceleration); // show on screen UI for the game. basegame. UI. rendergameui (INT) racinggame. player. gametimemiliseconds, // best time and current Lap (INT) racinggame. player. besttimems, racinggame. player. currentlap + 1, racinggame. player. speed * carphysics. meterpersectomph, // Gear logic with sound (cocould be improved ^) 1 + (INT) (5 * racinggame. player. speed/carphysics. maxspeed), // motormeter 0.5f * racinggame. player. speed/carphysics. maxspeed + // This cocould be improved 0.5f * racinggame. player. acceleration, racinggame. landscape. currenttrackname, highscore. gettop5highscores (); If (input. keyboardescapejustpressed | input. gamepadbackjustpressed) {// stop motor sound. stopgearsound (); // play menu music again sound. play (sound. sounds. menumusic); // return to menu return true;} // If (input. keyboardescapejustpressed) return false;} // render ()
Highscores
The highscores screen (see Figure 14-11) is very similar to the rocket Commander, but all online highscores are removed because network code or Web services are not implemented. The reason is that xNa lacks network support, but it may still be implemented in the PC version.
Figure 14-11
There are several helper methods in the highscores class, for example, to help you determine the current ranking in the game, but most methods have been used in the previous Games in this book.
The following code shows the top 10 players in the ranking. You will find that the auxiliary method code in the uirenderer class is very simple. If you only want to test the highscores game screen, use the internal unit test of the class, which is used to locate all the user interface elements, and other screens of the game do the same thing. I also used testdriven. Net in Visual Studio 2005, which can run the test again through the hotkey. In this way, I can test the code, press the hot key, and say, "Oh, no,". Press escape and fix the code until the class works normally. Most of the UI code is tested in this way.
// Go through all highscoresfor (INT num = 0; num <numofhighscores; num ++) {// show player in white if mouse is over line or else use gray color rectangle linerect = new rectangle (0, ypos, basegame. width, lineheight); color Col = input. mouseinbox (linerect )? Color. white: new color (200,200,200); // fill in text for this line basegame. UI. writetext (xpos1, ypos, (1 + num) + ". ", col); basegame. UI. writetext (xpos2, ypos, highscores [selectedlevel, num]. name, col); basegame. UI. writegametime (xpos3, ypos, highscores [selectedlevel, num]. timems, color. yellow); ypos + = lineheight;} // For (Num)
The remaining game screens are options, help, and credit. They are very similar to the highscores class and are not very exciting. Option has some good UI functions, allowing you to input text with the help of the input class, select one or more slide and drag them. Unit tests using the option class learn more about these features. The help and credit classes only display a texture on the screen, which is very similar to the splashscreen class you saw earlier.
Finally, you can click the exit button to exit the game because no other game screens will appear after the main menu is closed. All other game screens are always returned to the main menu (including the splashscreen class ).