Designing the Main Menu


During the end of the last chapter, you wrote the base class for the UI screens you will be creating in this chapter. The first user screen to create is the main menu. Everything in your game spawns from this screen.

Add a new code file to your project called UiScreens.cs, and add the code in Listing 6.1 to the file.

Listing 6.1. The MainUiScreen Class
 /// <summary> /// The main selection screen for the game /// </summary> public class MainUiScreen : UiScreen, IDisposable {     private Texture buttonTextures = null;     private Texture messageTexture = null;     private UiButton newButton = null;     private UiButton exitButton = null;     // Events     public event EventHandler NewGame;     public event EventHandler Quit;     #region IDisposable Members     /// <summary>     /// Clean up in case dispose isn't called     /// </summary>     ~MainUiScreen()     {         Dispose();     }     /// <summary>     /// Clean up any resources     /// </summary>     public override void Dispose()     {         GC.SuppressFinalize(this);         if (messageTexture != null)         {             messageTexture.Dispose();         }         if (buttonTextures != null)         {             buttonTextures.Dispose();         }         buttonTextures = null;         messageTexture = null;         base.Dispose();     }     #endregion } 

The two textures you are using in this class are for the background and the buttons. Although this menu has two buttons, each of the images for these buttons is stored in a single texture (called MainButtons.png in the media folder). This texture contains four different images, the on and off states of the New Game and Quit buttons. The other texture is the background of the screen, which the buttons will sit on top of.

Notice that you've also declared two instances of the actual button classes you designed during the last chapter. These classes handle the actual rendering of the button but are a "child" of this UI screen. You also have declared two events, which correspond to the user clicking on each of the two buttons.

Finally, you have declared the cleanup code for this UI screen. Because you will be creating each of the two textures in the constructor for this object, you need to ensure that you clean up both of them in this object. Also notice that the base class's Dispose method is also called to ensure that the Sprite object is cleaned up as well.

You might be surprised to find that trying to compile this code will fail; I know I was! There is currently no constructor defined for the MainUiScreen object; the default one that is provided has no parameters. However, derived classes must also call the constructor for the base class, and the base class for this object has no parameter-less constructor. Because the compiler does not know what default parameters to pass on to this method, it simply fails. Add the constructor in Listing 6.2 to your class to fix this compilation error.

Listing 6.2. Creating the MainUiScreen Class
 public MainUiScreen(Device device, int width, int height)     : base(device, width, height) {     // Create the texture for the background     messageTexture = TextureLoader.FromFile(device, GameEngine.MediaPath                      + "MainMenu.png");     // Mark the background texture as stretched     StoreTexture(messageTexture, width, height, false);     // Create the textures for the buttons     buttonTextures = TextureLoader.FromFile(device, GameEngine.MediaPath         + "MainButtons.png");     // Create the main menu buttons now     // Create the new game button     newButton = new UiButton(renderSprite, buttonTextures, buttonTextures,         new Rectangle(0,LargeButtonHeight * 1,         LargeButtonWidth, LargeButtonHeight),         new Rectangle(0,0,         LargeButtonWidth, LargeButtonHeight),         new Point((width - LargeButtonWidth) / 2,         (height - (LargeButtonHeight * 4)) / 2));     newButton.Click += new EventHandler(OnNewButton);     // Create the new game button     exitButton = new UiButton(renderSprite, buttonTextures,         buttonTextures, new Rectangle(0,LargeButtonHeight * 3,         LargeButtonWidth, LargeButtonHeight),         new Rectangle(0,LargeButtonHeight * 2,         LargeButtonWidth, LargeButtonHeight),         new Point((width - LargeButtonWidth) / 2,         (height - (LargeButtonHeight * 2)) / 2));     exitButton.Click += new EventHandler(OnExitButton); } 

The first thing you should notice is the call to base immediately after the constructor's prototype. I just described this part, informing the compiler about the default parameters to the base class's constructor, which allows this code to be compiled. After that, the textures are loaded, using the media path variable you declared in the main game engine. Notice that the StoreTexture method is called after the background texture is created, and notice that false is passed in as the last parameter to ensure the image is stretched onscreen rather than centered.

After the textures are created, the code creates the two buttons for this screen and hooks the Click event for each. As you see, the protected variable renderSprite is passed in to the constructor for the button, so they share the same sprite object as the UI screen. Quite a bit of fancy math formulas determine where the button will be located onscreen and where the button's texture is located in the main texture. Each button is one of the "large" buttons that you defined in your UI class yesterday, so these constants are used to determine where the buttons should be rendered in the texture. The constants are also used to determine the middle of the screen (width LargeButtonWidth) so they can be centered when they are rendered onscreen.

The image that contains the two buttons (and the two states of each) is a single 256x256 texture. Each button is 256 pixels wide by 64 pixels high. Modern graphics cards work well with textures that are square and that have heights and widths which are a power of 2 (which this texture is) and sometimes will not work at all without these restrictions. Knowing this, it is more efficient to combine the buttons into the single texture that meets the requirements, as you've done here. Common texture sizes range from 128x128 to 1024x1024 and can go even higher for highly detailed models if you want.

You might have noticed that the event handlers you used to hook the button click events used methods that you haven't defined in your class yet. You also need a way to render your UI, so add the code in Listing 6.3 to your class to take care of each of these issues.

Listing 6.3. Rendering and Handling Events
 /// <summary> /// Render the main ui screen /// </summary> public override void Draw() {     // Start rendering sprites     base.BeginSprite();     // Draw the background     base.Draw();     // Now the buttons     newButton.Draw();     exitButton.Draw();     // You're done rendering sprites     base.EndSprite(); } /// <summary> /// Fired when the new button is clicked /// </summary> private void OnNewButton(object sender, EventArgs e) {     if (NewGame != null)         NewGame(this, e); } /// <summary> /// Fired when the exit button is clicked /// </summary> private void OnExitButton(object sender, EventArgs e) {     if (Quit != null)         Quit(this, e); } 

The Draw method encompasses all the rendering that is required for the UI screen. Before any drawing is actually done, the BeginSprite method from the base class is called. In this case, simply calling the Begin method on the renderSprite object itself would have given us the same behavior, but encapsulating the call allows us to easily change the behavior later and let all subsequent derived classes get this new, improved behavior.

The base class was designed to handle rendering the background, so to render that, the Draw method is called on the base class. After that, it is a simple matter to call the Draw method on each of the buttons and mark the sprite as finished rendering.

The actual event-handler methods are amazingly simple. Depending on which button is clicked, you fire the appropriate event. However, if you remember from Chapter 5, "Finishing Up the Support Code," when you were designing the button, the only way to get the button to fire its click event was to call the OnMouseClick method on the button. On top of that, you needed to call the OnMouseMove method for the button to highlight correctly when the mouse moved over it. You also want the user to be able to control the UI without using the mouse. To handle these cases, add the three methods in Listing 6.4 to your class.

Listing 6.4. Handling User Input on Screens
 /// <summary> /// Update the buttons if the mouse is over it /// </summary> public void OnMouseMove(int x, int y) {     newButton.OnMouseMove(x, y);     exitButton.OnMouseMove(x, y); } /// <summary> /// See if the user clicked the buttons /// </summary> public void OnMouseClick(int x, int y) {     newButton.OnMouseClick(x, y);     exitButton.OnMouseClick(x, y); } /// <summary> /// Called when a key is pressed /// </summary> public void OnKeyPress(System.Windows.Forms.Keys key){     switch(key)     {         case Keys.Escape:             // Escape is the same as pressing quit             OnExitButton(this, EventArgs.Empty);             break;         case Keys.Enter:             // Enter is the same as starting a new game             OnNewButton(this, EventArgs.Empty);             break;     } } 

Nothing overly complicated here: you simply add new public methods to your UI screen that mirror the methods the buttons have. When they are called, you simply pass the data on to the buttons and allow them to do the work they need to do. The keyboard keys are a different matter, though. In this case, when the user presses a key, the code checks whether it is Esc, which is the same as pressing the Quit button, or Enter, which is the same as pressing the New Game button. This code allows the user to navigate the game without ever needing the mouse.



Beginning 3D Game Programming
Beginning 3D Game Programming
ISBN: 0672326612
EAN: 2147483647
Year: 2003
Pages: 191
Authors: Tom Miller

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