Using Blockers Base Classes


Just because you plan to replace the user interface you wrote for Blockers doesn't mean you need to write the entire thing from scratch again. Instead, you should take the code you already wrote for the base classes in Blockers and use it in this project. As in the last chapter, when you used the sample framework code files for the project, you want to add an existing file and add gui.cs from Blockers as well; only this time, you don't want to link the file, but instead, you just click Open and add it normally. This move has the effect of copying the code into your new project, so modifying it does not break your existing Blockers implementation.

You use fonts in this class this time (for rendering the text-box text later), because since you will have references to both System.Drawing.Font and Microsoft.DirectX.Direct3D.Font, you add the following using clause to this file:

 using Direct3D = Microsoft.DirectX.Direct3D; 

With that out of the way, you can get right to modifying the class to include the features you need and want for Tankers. You don't want the button sizes defined as part of the base class because they might change depending on the game (and size of your user interface items), so go ahead and remove those four protected constants from the UiScreen class now.

One of the things you might have noticed with the UiScreen class from Blockers is that the "background" image was required to be the full texture that was passed in. This requirement was fine with Blockers, but what if you have more than one screen stored per texture? What you need is a way to the rectangle source of the texture that will be used for the screen. The screen width and height member variables are stored in the StoreTexture method, which worked fine during the last game. However, because you'll be adding a second overload to StoreTexture, you don't want that code duplicated, so remove it from that method and put it into the UiScreen class constructor:

 // Store the width/height screenWidth = width; screenHeight = height; 

Now, you can replace the single StoreTexture method with the two different overloads in Listing 12.1.

Listing 12.1. Storing Textures for User Screens
 /// <summary> /// Store the texture for the background /// </summary> protected void StoreTexture(Texture background, int width, int height,     bool centerTexture) {     Rectangle computedSource = Rectangle.Empty;     if (background != null)     {         // Get the background texture         using (Surface s = background.GetSurfaceLevel(0))         {             SurfaceDescription desc = s.Description;             computedSource = new Rectangle(0, 0,                 desc.Width, desc.Height);         }     }     StoreTexture(background, computedSource, width, height, centerTexture); } /// <summary> /// Store the texture for the background /// </summary> protected void StoreTexture(Texture background, Rectangle source, int width,     int height, bool centerTexture) {     // Store the background texture     backgroundTexture = background;     backgroundSource = source;     // Store the centered texture     isCentered = centerTexture;     if (isCentered)     {         centerUpper = new Vector3((float)(width - backgroundSource.Width) / 2.0f,             (float)(height - backgroundSource.Height) / 2.0f, 0.0f);     } } 

As you can see, the first overload is the same as the one that was originally written for the last game; however, in this case, only the first step is performed, getting the full size of the texture. After that texture rectangle is calculated, the code calls the additional overload, which simply stores that texture and the appropriate size and calculates its position if the screen should be centered. Essentially, it's the same code as what you used the last time; it's just now broken into two separate methods to get the deriving classes more flexibility and freedom.

That's the only changes you need to make to the UiScreen class; now you can tackle that button class. Remember from the last game, you had to manually call the user input methods for keyboard and mouse on the button classes, which ended up with "spaghetti code"where your game engine user input code called your derived screen classes' user input code, which finally called the actual user input code for the buttons. If you want to talk about too many levels of indirection, that right there is the poster child. It would be significantly more maintainable if the button simply took care of this data itself, and luckily, you can make it do just that.

First, you add three new member variables to the UiButton class:

 private System.Windows.Forms.Keys shortcut; private GameEngine parentOwner = null; private bool enabled = true; // Should we handle user input? 

The first variable will be the "shortcut" key to this button. For example, the quit button's shortcut key could be Esc or some other variation. The second variable is the "owner" of the game engine whose events the button will want to hook. The last is whether the button is actually enabled. You wouldn't want a disabled button to be handling user input, would you?

Now you need some good way to get the events hooked. Add the method in Listing 12.2 to your UiButton class to handle hooking and unhooking the events.

Listing 12.2. Hooking User Input Events
 private void HookEvents() {     if (enabled)     {         // Hook the parents events         parentOwner.MouseClick += new MouseEventHandler(OnMouseClick);         parentOwner.MouseMove += new MouseEventHandler(OnMouseMove);         parentOwner.KeyDown += new KeyEventHandler(OnKeyDown);     }     else     {         // Unhook the parents events         parentOwner.MouseClick -= new MouseEventHandler(OnMouseClick);         parentOwner.MouseMove -= new MouseEventHandler(OnMouseMove);         parentOwner.KeyDown -= new KeyEventHandler(OnKeyDown);     } } 

Here the game owner is used to hook the events we care about (the mouse moving over the button, the mouse clicking on the button, or a keyboard click) when the button is enabled. If the button is disabled, we unhook the event.

Construction Cue

You may be wondering why you unhook the event when the button is disabled.

In .NET, when you hook an event such as the MouseUp event, each time an object hooks that event, the delegate used for the event handler is stored in a list. Each time that event is fired, each delegate in that list is called in the order it was created. So let's say you had two buttons, each of them hooking the MouseUp event. The button that hooks the event first is "enabled," whereas the button hooking the event second is "disabled." The user clicks that first button, which inside that delegate enables the second button. Now that the first button is finished executing its code, the next event in the queue is firedin this case, the second button's click handler. You've just clicked both buttons mistakenly because of the order in which the events were processed. It's much easier (and less error-prone) to simply unhook the events when you're not supposed to be listening to them.


You might have noticed that the events you just hooked don't actually exist anywhere in the game engine code. Add the following event declarations to your game engine code file:

 public event System.Windows.Forms.KeyEventHandler KeyDown; public event System.Windows.Forms.KeyEventHandler KeyUp; public event System.Windows.Forms.KeyPressEventHandler KeyPress; public event System.Windows.Forms.MouseEventHandler MouseMove; public event System.Windows.Forms.MouseEventHandler MouseClick; 

You also need some code that fires these events at the appropriate time. You already have the methods to handle the mouse and keyboard events, so that is the place to add this code. First, add this code to the OnMouseEvent method:

 if (MouseMove != null)     MouseMove(this, new System.Windows.Forms.MouseEventArgs(0, 0, x, y, wheel)); if (leftDown)     if (MouseClick != null)         MouseClick(this, new System.Windows.Forms.MouseEventArgs(0, 0, x, y, wheel));                                                                  wheel)); 

And then, add the OnKeyEvent method:

 if (keyDown) {     if (KeyDown != null)         KeyDown(this, new System.Windows.Forms.KeyEventArgs(key)); } else {     if (KeyUp != null)         KeyUp(this, new System.Windows.Forms.KeyEventArgs(key));     if (KeyPress != null)         KeyPress(this, new System.Windows.Forms.KeyPressEventArgs((char)key)); } 

Back in the gui.cs code file, you need something in the button code that actually calls this method. There are two good places to do so; the first is when the object is created. You need to modify the constructor parameters to accept an extra one, namely a GameEngine named parent, and then add these two lines to the end of the constructor's code:

 parentOwner = parent; HookEvents(); 

The other area where you want to ensure that this code gets called is when the user changes the state of the button. You need a property to get the state of the button along with the keyboard shortcut anyway, so add the two properties in Listing 12.3 to the class now.

Listing 12.3. Button Properties
 public System.Windows.Forms.Keys ShortcutKey {     get { return shortcut; }     set { shortcut = value; } } public bool IsEnabled {     get { return enabled; }     set { enabled = value; HookEvents(); } } 

The keyboard shortcut is a "normal" property with simple get/set items, but the enabled property is unique in that it calls the HookEvents method on set. This step ensures that no matter whether you enable or disable the button, the appropriate action takes place. You might also notice that the method declarations you had for the mouse before no longer compile because the signature of the method changed now that you're using the event handlers. Replace the mouse code with the code in Listing 12.4 (which also includes the keyboard code as well).

Listing 12.4. Handling User Input for Buttons
 private void OnMouseMove(object sender, MouseEventArgs e) {     if (enabled)     {         // Determine if the button is on or not         isButtonOn = buttonRect.Contains(e.X, e.Y);     } } private void OnMouseClick(object sender, MouseEventArgs e) {     if (enabled)     {         // Determine if the button is pressed         if(buttonRect.Contains(e.X, e.Y))         {             if (Click != null)                 Click(this, EventArgs.Empty);         }     } } private void OnKeyDown(object sender, KeyEventArgs e) {     if (enabled)     {         // See if the shortcut key was pressed         if (e.KeyCode == shortcut)         {             // It was, raise the event             if (Click != null)                 Click(this, EventArgs.Empty);         }     } } 

The check for enabled here probably isn't necessary, given that the events are unhooked when the object is disabled, but as I mentioned earlier, it's possible for a disabled button that was recently enabled to be clicked because of the order of the event messages. It's also possible for a recently disabled button to be clicked as well. This code handles that case. The mouse handlers are pretty much unchanged outside of the arguments passed in, and the keyboard method is self-explanatory. If the user presses the magic shortcut key, the click event is fired just as if he or she clicked the button.



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