Latest  | Search | Go
Edit this page   |   Attach file 

  Home | Tutorials | Technical Reference | Runtime | API Documentation | CreatingGUIs  

Creating GUIs with Script and the Visual Designer

Tutorial Author:Jeremy Stieglitz


gui_visualdesigner.jpg
Configuring the layout of an MGUIDialog in the Visual Designer

Overview of GUI scripting

The ScriptingSystem's GUI wrappers allow you to create entire GUI and menu systems entirely in C# script. Whether your interest is in a basic Main Menu front-end or a complex Real Time Strategy GUI, you can pull it off in script. To begin with, let's take a look at how the overall GUI system is structured. There are 2 types of core GUI objects: MGUIDialog and MGUIControl (each wrapping a respective RealityEngine class). MGUIDialogs contain MGUIControls. Let's take a look at some code that initializes a new MGUIDialog:

public class GUI_Sample : ScriptingSystem.MGUIDialog
{
   public GUI_Sample()
   {
         // some basic MGUIDialog properties
         this.Location = new System.Drawing.Point(0, 0);
         this.Size = new System.Drawing.Size(610, 380);
         this.EnableCaption = true;
         this.Text = "Sample";
         this.Draggable = true;
         this.EnableMouseInput = true;
         this.DrawBackground = true;
         // don't want to do this just yet, without the Event's function defined!
         this.DialogEvent += new SSystem_DialogEvent(dialog_DialogEvent);

         Visible = true;
         Enabled = true;
    }
}

The first thing to learn from this code is that your GUI classes will typically inherit from ScriptingSystem.MGUIDialog. So let's look at what this sample dialog constructor constructor does. First it sets its default dialog location on the screen, and its dialog size (these can also be set in the Visual Designer as will be discussed in the next section). It sets some other basic properties of the MGUIDialog, such as a caption and draggability. It also sets an Event Handling function that will be triggered by events from its Controls; we'll look at the makeup particular function later on.

Now you know how to construct a new MGUIDialog. If you were to use the above sample code and create a "new GUI_Sample();" in your GameCore constructor (or somewhere in your application), you'd then see this dialog on the Main Menu! But a MGUIDialog without any controls would just be a blank form, so let's see how we can add a simple control to the MGUIDialog, a button:

public class GUI_Sample : ScriptingSystem.MGUIDialog
{
   // a simple button control
   ScriptingSystem.MGUIButton SampleButton;

   public GUI_Sample()
   {
         // some basic MGUIDialog properties
         this.Location = new System.Drawing.Point(0, 0);
         this.Size = new System.Drawing.Size(610, 380);
         this.EnableCaption = true;
         this.Text = "Sample";
         this.Draggable = true;
         this.EnableMouseInput = true;
         this.DrawBackground = true;
         // don't want to do this just yet, without the Event's function defined!
         //this.DialogEvent += new SSystem_DialogEvent(dialog_DialogEvent);

         Visible = true;
         Enabled = true;

         // create the new button, set some basic properties, and add it to the dialog
         this.SampleButton = new ScriptingSystem.MGUIButton();
         this.SampleButton.Location = new System.Drawing.Point(305, 190);
         this.SampleButton.Size = new System.Drawing.Size(140, 50);
         this.SampleButton.Text = "Sample Button";
         this.Controls.Add(this.SampleButton);
    }
}

Pretty simple stuff. We just have a MGUIButton ("SampleButton") reference declared at the top, then somewhere (can be anywhere) in the MGUIDialog's constructor we initialize the SampleButton reference, set its size location and text, and add it to the MGUIDialog's Controls list. Then it will be visible on the MGUIDialog, ready for clicking!

Of course, we'll also want to handle Events from this button press so that we can have the dialog actually do something, interact with the game in some way. We'll discuss that further below. Before we dig into the aspects of Event Handling, let's examine the complete process for setting up MGUIDialog's layouts in the Visual Designer.

Creating your GUI's layout in the Visual Designer

Creating the form layouts for your GUI in the Microsoft Visual Designer (Visual Studio .NET 2005 IDE only) is a powerful tool that greatly simplifies the work required to create and modify detailed menus. Instead of writing code to set up and configure the default properties for GUI controls, you can create and adjust them all visually, with the skinned appearance exactly as they are in Reality itself.

To do this, first open the "GuiDesigner" solution (WindowsApplication1 sub-project) located in the Scripts directory in the Eval Kit.

Next, we're going to want to add a basic MGUIDialog C# file to the "WindowsApplication1" sub-project, to give us something to start work with. Right-click on the "WindowsApplication1" sub-project in the Solution Explorer and select "Add... New Item". In the "Add New Item" dialog box, select "Component Class" (not Class as the following image indicates) and type in a unique name, such as "GUI_Sample.cs". The process is depicted here:

addnewitem.gif

You will then see the GUI_Sample.cs (as per this example) file listed in the WindowsApplication1 project. Next, right-click on GUI_Sample.cs in the Solution Explorer and select "View Code." Then, overwrite all the code in the file with the following code:

using System;
using System.Collections;
using ScriptingSystem;

public class GUI_Sample : ScriptingSystem.MGUIDialog
{
   public GUI_Sample()
   {
      InitializeComponent();

      Visible = true;
      Enabled = true;
   }
   private void InitializeComponent()
   {
      this.EnableCaption = true;
      this.EnableMouseInput = true;
      this.Location = new System.Drawing.Point(0, 0);
      this.Size = new System.Drawing.Size(270, 120);
      this.Text = "Dialog Caption";
   }
}

This code is a basic template for a Visual Designer compatible MGUIDialog, but be sure to change the two "GUI_Sample" text instances in that code to whatever your actual dialog's classname/filename is! Finally, Save & Close the the file (File/Close), and then double-click it in the Solution Explorer to open it in Design View (refreshes the Visual Designer from the code change).
You'll see this:

designview.gif

Now you're ready to start putting Controls on the dialog!

For that, we now must add the GUI Designer controls to the Visual Designer Toolbox (you only have to do this once). Ensure that the Visual Studio "Toolbox" panel is open (press Ctrl-Alt-X if it's not), and then right-click on the Toolbox and select "Choose Items".

chooseitems1.gif

Then click "Browse" in the "Choose Toolbox Items" window that pops up. In the ensuing Open-file dialog, navigate to your Reality installation's "bin\System" directory and select the "GUIDesigner.dll". Then click Open and OK in the "Choose Toolbox Items" window, and you'll have the controls available in the Toolbox!

chooseitems2.gif

Now that you have the GUI Designer controls available, you can select one and then drag a box on the dialog, and the Control will appear on the dialog for further modification. Each GUI Designer control has different properties and appearance (open the Properties Window is open with Alt-Enter), so try them out to see what functionality suits your needs. Critically, they also have different methods and properties available during Event Handling, which we'll discuss in the next section.

The most important thing to keep in mind is that you can use the Visual Designer project to set up your dialog's control layout, but it does not have knowledge of the full suite of Wrappers functions or your Game classes which may be necessary for complex Event Handling. Consequently, it is advisable to add the very same GUI_Sample.cs file (or whatever you have named it) to your Reality "Scripts" project ("Game" subfolder), so that you can do the internal dialog coding (Event Handling) there with access to your Game Script classes.

GUI Event Handling

While you can set up the layout of your GUI in the Visual Designer, you will need to add an Event Handling function for your dialog in order for your dialog's controls to actually do things. The basic idea of Event Handling is that you assign a numerical ID (ControlID) to every Control that you want to handle, and then use a switch statement within the Event Handling function to process a message triggered by (passing) that particular numerical ID. To understand exactly how this works, let's examine a simple dialog. This dialog will contain a button that will close the MGUIDialog when it is clicked. The code is as follows:

public class GUI_Sample : ScriptingSystem.MGUIDialog
{
   private ScriptingSystem.MGUIButton CloseButton;

   // the Control ID's for this dialog's Event-Handled Controls
   enum ControlHandles
   {
      IDC_CLOSEBUTTON
   };
   public GUI_Sample()
   {
         InitializeComponent();

         this.EnableCaption = true;
         this.Draggable = true;
         this.EnableMouseInput = true;
         this.DrawBackground = true;
         // Register the Event Handling function
         this.DialogEvent += new SSystem_DialogEvent(dialog_DialogEvent);

         Visible = true;
         Enabled = true;

         // Set up this dialog's Controls' ControlIDs
         this.CloseButton.ControlID = (int)ControlHandles.IDC_CLOSEBUTTON;
      }

   // Registered as the dialog's Event Handling function in the dialog's contructor
   void dialog_DialogEvent(uint Event, int ControlID, MGUIControl Control)
   {
      switch ((ControlHandles)ControlID)
      {
         case ControlHandles.IDC_CLOSEBUTTON:
            // CloseButton pressed Event will hide the window
            Visible = false;
            break;
      }
   }

   private void InitializeComponent()
   {
      this.CloseButton = new ScriptingSystem.MGUIButton();
// 
// CloseButton
// 
      this.CloseButton.Location = new System.Drawing.Point(100, 55);
      this.CloseButton.Size = new System.Drawing.Size(70, 32);
      this.CloseButton.Text = "Close";
// 
// GUI_Sample
// 
      this.Controls.Add(this.CloseButton);
      this.EnableCaption = true;
      this.EnableMouseInput = true;
      this.Location = new System.Drawing.Point(0, 0);
      this.Size = new System.Drawing.Size(270, 120);
      this.Text = "Sample Dialog";

   }
}

The above code adds a couple new concepts to our GUI repertoire. First, it uses a private enumeration for the ControlID's, calling it enum ControlHandles. Using an enum makes it easier than having to declare unique values manually for each ControlID on the dialog, and every dialog can simply use a private enum called ControlHandles for consistency. Next, it sets the dialog's DialogEvent (using the += operator for Events) to a new SSystem_DialogEvent that's created taking the handling function (which can be named anything, here it's "dialog_DialogEvent") as input.

Finally at the end of the constructor (you can put this anywhere after the Controls are initialized), the dialog sets the ControlID's of its particular Event-Handled Controls, in this case just CloseButton. It sets the CloseButton.ControlID to the desired enumerated ID for that particular Control, casting it to an int (which is what ControlID actually is, though again it is prudent to simply use enums so you don't have to worry about the actual values in question).

Now that the enums are set, the Event Handler registered, and the ControlID's assigned, we're ready to dissect the Event Handling function itself. As you can see, dialog_DialogEvent is quite simple. First, the function casts the ControlID it is passed to a ControlHandles enum (since we'll opt to use the enum in our switch statement, saving us the trouble of working with numerical values). Next it begins the switch statement with that particular ControlHandles value. Finally it has one switch case for the CloseButton's ControlHandle, which simply sets the Visible = false, effectively hiding and deactivating the dialog when the CloseButton is pressed.

That's essentially all there is to Event Handling! Just one more thing to bear in mind: specific MGUIControls (sliders, checkboxes, combo boxes, edit boxes, etc) may have the Event Handler triggered upon something specific to their functionality. When and why is always quite obvious when you think about the Control type -- namely when a Control receives input that affects its purpose. For instance, MGUIEditBoxes will trigger the Event Handler when their text value is changed by the user, MGUICheckBoxes when their Checked value is changed by clicking, MGUIComboBoxes when a new combo list entry is selected, etc. To see how this concept applies to something like an MGUIListBox, let's look at a snippet of an Event Handler case for a particular MGUIListBox:

   void dialog_DialogEvent(uint Event, int ControlID, MGUIControl Control)
   {
      switch ((ControlHandles)ControlID)
      {
         case ControlHandles.IDC_LISTBOX1:
            this.StaticLabel.Text = ((MGUIListBox)Control).SelectedItemText;
            break;
      }
   }

In the above code, notice that the switch case for the ListBox casts the Control object to an MGUIListBox, which was the MGUIControl type to which IDC_LISTBOX1 was assigned in the constructor (not depicted here). Notice that the MGUIListBox.SelectedItemText Property is used to get the text entry of the currently selected item, and since the Event Handler will be triggered any time a new item is selected in that MGUIListBox, the StaticLabel. Text will change accordingly. (Note you can also set an integer value to each of an MGUIListBox's entries, as an alternative to bind data to entries.)

Now you know how to create and use an Event Handling function to make a dialog's Controls actually do things. Control Events could have an effect on the dialog itself, on the greater game, on other dialogs, the possibilities are endless, it all comes down to what you want to do in your game's script code. Having learned the basics of the GUI system (comprising MGUIControls in MGUIDialogs), the process of setting up a Dialog's layout in the Visual Designer, and handling events in the dialog's code, you may be ready for some more advanced concepts to allow you to further extend the power of your game's GUI usage. If so, follow me to the next section and let's delve deeper!

Advanced GUI functionality

To begin our examination of advanced GUI features, there are several special functions and classes in the GUI system that bear mentioning. The first is the GUI's MessageBox dialog, which you can set up as a scripted dialog. The GUI MessageBox is displayed when game code calls the GUISystem.DisplayMessageBox(string title,string text,int blockTimeSeconds). For the blockTimeSeconds specified, all dialog inputs save for the MessageBox itself will be blocked. This allows you to effectively lock the game's GUI as you wish, for instance the MessageBox is used with 5 blockTimeSeconds when "Connecting" as a Client, while searching for a Server.

If you wish to end the blocking early (say, if a Server is found), just call GUISyste.EndMessageBoxBlocking(). If you use 0 as blockTimeSeconds, no blocking will occur and the user will immediately be able to click "OK" to close the MessageBox. As stated, you can configure the appearance of the MessageBox completely via script, since in fact you can use any arbritrary dialog as the MessageBox dialog: simply call "MGUISystem.SetMessageBoxWindow(Window);" in the dialog's constructor, and that dialog will be used as the MessageBox.

You should also ensure, however, that you have a MGUIStatic (text label) and a MGUIButton on the dialog. The MGUIButton ("OK") should set "Window.Visible = false;" in your Event Handler for that button, for obvious reasons. Importantly, the MGUIStatic should have a ControlID of -1 so that Reality automatically sets the appropriate text label when the DisplayMessageBox function is called. Other than that requirement, you can configure your MessageBox dialog any way you want as suits your game.

chooseitems3.gif

Another special type of dialog is the Video Settings dialog. This dialog with all its Controls is actually built into the Reality API because it needs low-level access to efficiently change the core video settings. You don't have to use it in your game (since there are other ways to do basic things like change the resolution and fullscreen/windowed states), but it will benefit your game's flexibility while in development to have this window since it does provide a lot of options. You can in fact customize this Video Settings as much as you want if you use "MGUIDialog.GetControl(ID)" to adjust the controls created automatically by the API, but to begin with let's look at how to simply create the Video Settings dialog itself.

To use the Video Settings dialog, what you do is create your own scripted dialog that inherits from MVideoSettingsDialog instead of MGUIDialog. This will make your new class a derivation of the Video Settings dialog itself, and then you can control its display like any other Window. Let's look at the code that does this:

public class GUI_VideoSettings : MVideoSettingsDialog
{
   private const int IDC_CANCEL = 1;
   private const int IDC_OK = 2;

   public GUI_VideoSettings()
   {
      try
      {
         this.EnableCaption = true;
         this.Draggable = true;
         this.EnableMouseInput = true;
         this.DrawBackground = true;
         this.Text = "Video Settings";
         this.DialogEvent += new SSystem_DialogEvent(dialog_DialogEvent);
         this.Size = new System.Drawing.Size(500, 500);

         Visible = false;
         Enabled = true;
      }
      catch (Exception ex)
      {
         this.Text = ex.StackTrace;
      }
   }
   void dialog_DialogEvent(uint Event, int ControlID, MGUIControl Control)
   {
      MGUISystem.HandleVideoSettingsCallback(Event, ControlID, Control);
      switch(Control.ControlID)
      {
         case IDC_CANCEL:
            Window.Visible = false;
            break;
         case IDC_OK:
            Window.Visible = false;
            break;
      }
   }
}

Note that this dialog inherits from MVideoSettingsDialog, and that in the dialog's Event Handler, the dialog passes the Event to a special GUI System function, HandleVideoSettingsCallback. This allows input on the various MVideoSettingsDialog controls to be handled by Reality itself, so that it will react/refresh appropriately as the user changes video settings. Alternatively you could handle all the Control Events yourself, but that'd be more complex. Here we do however handle the Cancel and Ok button presses to close the Window (they too are passed to MVideoSettingsDialog, which will apply the settings when OK is clicked, but not close the Window).

Now if you've got a bunch of Dialogs-Windows which you've created, but you want to tie them together into some kind of overarching menu system for your game, there are various ways you can approach doing so. If you want them to be active while the game itself is being rendered (and presuming you're not hiding the mouse), then you will not want your Windows to be DesktopWindows. DesktopWindows will hide themselves when the Escape-Menu Desktop is itself hidden, the Desktop simply being a convenience for you to keep your menus separate from your gameplay if you desire. Either way, you'll need to create your GUI Dialogs in a global manner (the GameCore constructor is a fine place), and control their visibility state in some fashion that's meaningful to your game. In the Eval Kit, this is done via a Main Menu Dialog which has buttons that open other Dialog-Windows which it references. Let's look at what the Main Menu does exactly to manage these sub-Dialogs:

public class GUI_MainMenu : ScriptingSystem.MGUIDialog
{
   private GUI_NewGame DialogNewGame;
   private GUI_GraphicsOptions DialogGraphicsOptions;
   private GUI_GameOptions DialogGameOptions;
   private GUI_VideoSettings DialogVideoSettings;
   private GUI_MessageBox DialogMessageBox;
   ...
   public GUI_MainMenu()
   {
   ...
   DialogNewGame = new GUI_NewGame();
   DialogGameOptions = new GUI_GameOptions();
   DialogGraphicsOptions = new GUI_GraphicsOptions();
   DialogVideoSettings = new GUI_VideoSettings();
   DialogMessageBox = new GUI_MessageBox();

   this.EnableCaption = true;
   this.Draggable = false;
   this.EnableMouseInput = true;
   this.DrawBackground = false;
   this.Text = "Reality Engine Eval Build " + MHelpers.RealityBuildVersion;
   this.DialogEvent += new SSystem_DialogEvent(dialog_DialogEvent);

   Visible = true;
   Enabled = true;
   }
   void dialog_DialogEvent(uint Event, int ControlID, MGUIControl Control)
   {
      switch(Control.ControlID)
      {
         case IDC_NEWGAME:
            DialogNewGame.Window.Visible = true;
            DialogNewGame.Window.BringToTop();
            break;
         case IDC_VIDEOSETTINGS:
            DialogVideoSettings.Window.Visible = true;
            DialogVideoSettings.Window.BringToTop();
            break;
         case IDC_GRAPHICSOPTIONS:
            DialogGraphicsOptions.Window.Visible = true;
            DialogGraphicsOptions.Window.BringToTop();
            break;
         case IDC_GAMEOPTIONS:
            DialogGameOptions.Window.Visible = true;
            DialogGameOptions.Window.BringToTop();
            break;
         case IDC_EXIT:
            MHelpers.ShutdownApp();
            break;
      }
   }
...
}

What the GUI_MainMenu does is initialize its sub-menus and then set their Visible = true when the appropriate main menu button is clicked. It also brings each Window to the top of the ordering list with BringToTop(), when the appropriate button is clicked. In this way, the GUI_MainMenu controls the display of the other Windows (if desirable, it could also hide all extraneous Windows when a particular button is pressed). Furthermore, the GUI_MainMenu specifies its Window as IsDesktop, namely it becomes a special type of Window which automatically resizes itself to the application canvas size, which never appears on top of other DesktopWindows, and the visbility of which (as with all DesktopWindows) can automatically be toggled with Escape (alternatively you could implement this kind of functionality manually entirely within Script if you are so inclined).

Another topic for advanced GUI manipulation is the overridable OnRender() event. This is called every frame for every initialized Dialog, before the Dialogs are rendered, regardless of whether the Dialog is actually Visible. By default, the OnRender() function does nothing. However, you can override this function to do a multitude of things: for instance, rendering your own custom Window background (just draw a box at the location/size of the dialog), drawing 3d elements, resizing or repositioning controls as you see fit (to account for for resolution changes), or manually maintaining & rendering Dialogs outside of the GUI Window system (using MGUIDialog.Render(float elapsedTime) to manually draw a dialog for a delta-time). Let's see what the GUI_MainMenu does to draw a watery background behind the dialog and reposition one of the PictureLabel controls (in this case, the Reality Engine graphic on the right-side of the screen):

public override void OnRender()
   {
      if (!MGUISystem.DesktopVisible || !Minimized)
         return;

         this.RealityLogo.Location = new System.Drawing.Point(MCanvas.Width - this.RealityLogo.Size.X, 0);

         BackgroundTexture.uTile = 2.0f;
         BackgroundTexture.vTile = 2.0f;
         BackgroundTexture.uOff = 0;
         BackgroundTexture.vOff = -MHelpers.Seconds / 7.0f;
         MCanvas.Box(MHelpers.ColorFromRGBA(255, 255, 255, 70 + (int)Math.Abs((float)Math.Sin(MHelpers.Seconds) * 90.0f)), 
         -2, -2, 1028, 772, BackgroundTexture, MBlendMode.MBLEND_SRCALPHA, MBlendMode.MBLEND_ONE);
   }

What this does, first of all, is quickly bail out if the Window is not visible or minimized -- in which case it's not worth our time to update the Controls and we certainly don't want to draw the custom BG. Then, if we don't want to bail out, let's offset the RealityLogo from the right side of the screen, so that even as the canvas size changes (say, if the app is resized or resolution changed), the RealityLogo stays locked in position relative to the right-side. Then, let's draw a custom background "MCanvas.Box()" with a scrolling texture. Note, specifically all MCanvas draws take place at a virtual (scaled) 1024x768 resolution. This is different from the GUI system sizes and locations, which are absolute pixel values.

The MCanvas ues a virtual 1024x768 resolution to simplify the scaling In-Game HUD scaling and on-screen graphics, making it automatic, particularly since mip-maps may be involved. Thus this MCanvas.Box call is made with the 1024x768 virtual canvas size in mind, while the RealityEngine.Location deals in absolute pixels. It's important to keep this difference in mind if you integrate MCanvas draws with your GUI graphics as we do here (you can, if desired, alter the Canvas scaling with the MCanvas.ScalingX and Mcanvas.ScalingY Properties, and disable it altogether by setting 1,1 for both those Properties before you draw something).

Now you know how to create a custom MessageBox, VideoSettings dialog, Main Menu system & relations between Dialogs, and custom Dialog drawing! Finally, note that you can create your own skinned GUI System graphic by altering the dxutcontrols.tga file found in "Textures\Core" (for Reality runtime), and "Scripts\Resources\dxutcontrols.png" for the GUI Visual Designer.

Attachment sort Action Size Date Who Comment
gui_visualdesigner.jpg manage 64.3 K 08 Dec 2004 - 17:43 Main.guest  
addnewitem.gif manage 31.3 K 30 Jan 2005 - 09:41 Main.guest  
designview.gif manage 4.5 K 12 Dec 2004 - 17:57 Main.guest  
chooseitems1.gif manage 11.2 K 12 Dec 2004 - 18:00 Main.guest  
chooseitems2.gif manage 41.3 K 12 Dec 2004 - 18:00 Main.guest  
chooseitems3.gif manage 15.3 K 12 Dec 2004 - 18:01 Main.guest  

CreatingGUIs   Edit | Attach | Ref-By | Printable | Diffs | r1.15 | > | r1.14 | > | r1.13 | More