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
///
/// The main selection screen for the game
///
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
///
/// Clean up in case dispose isn't called
///
~MainUiScreen()
{
Dispose();
}
///
/// Clean up any resources
///
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
///
/// Render the main ui screen
///
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();
}
///
/// Fired when the new button is clicked
///
private void OnNewButton(object sender, EventArgs e)
{
if (NewGame != null)
NewGame(this, e);
}
///
/// Fired when the exit button is clicked
///
private void OnExitButton(object sender, EventArgs e)
{
if (Quit != null)
Quit(this, e);
}
|