Introduction
So you decided to write that next big game. You got a nice virtual world and a player model up and running. Then reality struck, you got no user interface! Hours later, you end up looking at the CustomUI sample from the DirectX SDK. Reality struck #2, there’s like almost no documentation for this sample!
The best way to figure out the CustomUI sample is to tinker around with it. I wrote this guide as a quick read on the things that I’ve found during my own tinkering session. This may help you on your own tinkering adventure.
The CustomUI project is probably the more interesting and useful sample from the DirectX SDK because nearly every game needs some sort of user interface. I’ve spent some time reading and analyzing the C# version of the sample, and I’ll try to share what I have learned here.
Design
The people who designed it basically modeled it after a Windows control. There’s a Dialog
class that can hold a collection of controls. After initializing the dialog, you can add controls to it using several methods provided by the Dialog
class.
Figure 1 – The figure shows that you can have more than one dialog on the screen. Dialog A has two controls, while Dialog B has three controls. Note that controls cannot exist outside of a dialog.
The sample uses the sampleFramework
which is just a class that performs all of the basic operations common to DirectX applications. It’s actually not a bad way to start your game using it. The sampleFramework
has many methods to perform various tasks, and is beyond the scope of this guide. However, there are two callback functions that you should be aware of, OneFrameMove
, and OnFrameRender
. OnFrameMove
should contain code that changes the state of the screen but does not draw it such as processing game events. OnFrameRender
should contain code that actually draws the scene.
Adding a Control to the Dialog
Take a look at the following code from line 394:
Checkbox clearEdit = sampleUi.AddCheckBox(ClearEditControl,
"This checkbox controls whether the edit control " +
"text is cleared when Enter is pressed.",
150, 460, 430, 24, false);
This adds a checkbox control to the sampleUi
dialog and returns the added checkbox. The first parameter is the ID (ClearEditControl
is a static int
declared on line 59), the second is a description of the text next to the checkbox. The fourth, fifth, sixth, and seventh are the position and size of the control, and the last parameter is to set whether the control is the default control for the dialog.
The position of the control is relative to the dialog. For example, if the sialog is at position (50,100) and a control inside the dialog is set at (10,10), the control’s screen position would be (60, 110).
Another thing to note is that the AddCheckBox
method of sampleUi
is just a wrapper that just calls on AddControls
to set the checkbox and add it to the dialog. There are other wrapper methods for the rest of the controls.
Control Rendering
For every dialog you add, you need to call its OnRender
method in your OnFrameRender
callback function. From there, the dialog is drawn, and all the controls inside the dialog OnRender
are called as necessary. The control’s OnRender
is where the actual control is drawn.
The DXUTControls.dds file contains the texture that is used to draw some of the controls. It is loaded on dialog initialization and is loaded into the DialogResourceManager
. The DialogResourceManager
makes sure that there’s only one copy of the assets used by all dialogs to conserve memory.
Controls are drawn in layers. For example, the button control has a layer that draws the button shape, and a layer that draws the button text. There’s an Element
object for each layer which defines what texture or font to use for drawing. The InitializeDefaultElements
method defines the default setting for each layer.
Control Creation
There’s a base Control
class that all controls derive from. This base class contains the basic properties common in all controls. Controls are created in dxmutgui.cs and must inherit the base Control
class. The OnRender
method should be overridden with custom code to draw the control.
Drawing
Controls have access to their parent dialog via the control’s Parent
property. Dialog
contains all the drawing functionality including DrawText
, DrawSprite
, DrawText
, and DrawRectangle
. They all take an Element
class as a parameter.
The Element
class contains information such as indexes to the texture or font to use, but does not contain the actual texture or font. The actual textures and fonts are stored in the DialogResourceManager
class. This ensures that there’s only one copy of the same texture or font loaded at all times.
To write text, you would call the dialog’s DrawText
method passing in the text, Element
, and rectangle specifying the position (relative to the dialog). DrawText
would use the Element
passed in to determine which font to use for rendering.
Likewise, to draw a texture, you would call the dialog’s DrawSprite
method passing in an Element
(which contains the index to the texture) and the rectangle containing position information.
User Input Processing
As you probably know, Windows communicates internally by using messages. sampleFramework
’s MessageProc
can intercept these messages. You then call the dialog’s MessageProc
method which in turn relays it to the control’s MsgProc
method. The messages are broken up and processed, and calls are made to HandleKeyboard
and HandleMouse
. Derived controls can override the HandleKeyboard
and HandleMouse
methods to add keyboard and mouse handling.
So, when you add your own dialog, you need to add an entry to call the dialog’s MessageProc
. Here’s an example from line 340:
noFurtherProcessing = hud.MessageProc(hWnd, msg, wParam, lParam);
if (noFurtherProcessing)
return IntPtr.Zero;
Handling a Control’s Events
Handling control events is pretty simple. You just create a method for handling the event and bind it to the control’s event with an EventHandler
. For example:
edit.Changed += new EventHandler(OnEditChanged);
edit
is a textbox. When its text is changed, OnEditChanged
will be called. Event binding should be called after adding the control to the dialog.