TGB/MiniTuturials/GUIOptionsMenu

From TDN

Back

Adding an Options Menu
Description:

This example will show you how to use the GUI Builder to create a simple options menu for your game. This tutorial is an extention of Main Menu with Simple Buttons but it can be applied to Main Menu with Bitmap Images aswell.

This tutorial is written for beginners who are fairly new to the GUI Builder and have already completed Main Menu with Simple Buttons. If this is your first time using the GUI Builder, we strongly suggest you look over the Main Menu with Simple Buttons because there are some basic techniques from that tutorial that are used in this tutorial and may not be fully explained or displayed here.

Stuff used or explained in this tutorial:

  • Several Gui Controls:
    • GuiButtonCtrl
    • GuiTextCtrl
    • GuiRadioCtrl
    • GuiCheckboxCtrl
    • GuiTextEditCtrl
  • Getting two GUI's to interact
  • Accessing GUI controls using their Internal Names
  • Exporting values to and reading values from a custom config file






Before we get started make sure you are in the same project with your main menu. If you don't have that project folder for whatever reason, download it: MainMenuTextGui Example Project Folder

The first thing we want to do is edit our original menu to add an options button. Open up your main menu project, switch to the GUI editor, and select 'MainMenuGui' in the middle dropdown list up top.

Image:MainMenu ButtonsNamed.JPG

You should see our good ol' main menu from before. If you need to, scale up the background to make room for the new button. Then right-click the background and make sure it's 'highlighted' with a yellow/green border. Then select 'GuiButtonCtrl' from the top 'Create Control' list up at the top left. Place it wherever you want and set it's 'Text' field to "Options". Now set it's 'Command' field to "mainMenuGui.options();".

Image:OptionsMenu OptionsButton.JPG

That's all we need to do to our main menu. Make sure you save your main menu gui before continuing.

Now create a new GUI for our options menu ('File>New GUI...') named "optionsMenuGui" with the default GUI Class ('GuiControl'). This time, rather than setting the background to 'GuiModelessDialogProfile', we are going to leave it at it's default, which is 'GuiDefaultProfile'. We are doing this specifically because we don't want the user to be able to click through to anything else while the options menu is open!

With our clear background ready we can focus on the part people will see. Click the 'New Control' list (the first dropdown list on the top-left of the window) and select 'GuiControl'. Move the new box towards the center and set it's profile to 'GuiWindowProfile' so we can see it. Scale it to whatever size you want your menu to be. Keep in mind we're going to have three buttons!

Image:ScoreboardGui WindowGrey.JPG

Right-click on your new menu background to highlight it and create a 'GuiTextCtrl' Via the 'New Control' list. The text control should appear inside the menu. If it didn't, delete the text control and try again making sure that the menu box was 'highlighted', not 'selected'.

Image:OptionsMenu RadioLabel.JPG

Set the text field of the new text control to "Radio Buttons:" and press enter. Then scale the box to fit snugly around the text.

Image:OptionsMenu RadioLabelText.JPG    Image:OptionsMenu RadioLabelScale.JPG

Now let's create some radio buttons. 'GuiRadioCtrl' via the 'New Control' list. Scale the border of the button down so it fits snugly around the text, and then make two copies of it using 'Copy' and 'Paste'. Place your radio buttons where you want them.

Image:OptionsMenu FirstRadio.JPG

Now let's edit the text on our new radio buttons. Edit the 'Text' field of the buttons to say "Radio A", "Radio B", and "Radio C" respectively.

Image:OptionsMenu RadioNames.JPG

As you probably know, radio buttons are designed for only one radio button in each group of radio buttons can be selected at once. In order for these radio buttons to know that they are in the same group, we have to set their 'GroupNum' fields to the same value. Let's set all the radio buttons to have 'GroupNum' set to "1".

Image:OptionsMenu RadioGroup.JPG

Now add a checkbox: select 'GuiCheckboxCtrl' from the 'New Control' menu. Move it below your radio buttons, set it's 'Text' field to "Checkbox", and scale the border down to an appropriate size.

Image:OptionsMenu Checkbox.JPG

Ok, let's add one more control before we add our buttons: a text field! First we'll need a label for it. Select 'GuiTextCtrl' from the 'New Control' menu, and this time set it's text to 'Name'.

Image:OptionsMenu NameLabel.JPG

Now select 'GuiTextEditCtrl' from the 'New Control' menu. Place it next to the 'GuiTextCtrl' and scale it to an appropriate size.

Image:OptionsMenu NameField.JPG


All that's left to do now is add our buttons. We want two buttons: "Apply" and "Cancel". If you need a refresher on how to create a button, check out how we made the "Options" button on the Main Menu earlier in this tutorial. Just like before we will be using the menu's namespace for our menu commands. Set the 'Command' field on the "Apply" button to "optionsMenuGui.applyChanges();". Then set the 'Command' field on the "Cancel" button to "optionsMenuGui.cancelChanges();".

Image:OptionsMenu Buttons.JPG

Now we need to make sure that our script can access the values of all the fields. Previously (as in Score and Time GUI) we gave our controls global names. Rather than cluttering the global namespace, let's use a different method: Internal Names.

You may have noticed a field called 'Internal Name' on GUI controls. This is a handle attached to that element that can be used to locate it from it's parent control. In our case we have our main named 'GuiControl' that has a global name ("optionsMenuGui"). If you look at the tree view, you can see that it has one child, a 'GuiControl' that acts as our window background. That 'GuiControl' in turn has several children - which are all our controls that are within that window.

If this all sounds confusing, don't worry. It should make sense a little later when we implement it. For now, lets set some internal names. First we need to set the grey box's internal name so we can later access it's children. Select the grey box (if it is still 'highlighted', you might need to right click on the green background before you can select it) and set it's 'Internal Name' field to "optionsMenuBox" and press enter.

Image:OptionsMenu InternalName.JPG

Now set the radio buttons' 'Internal Name' fields to "radioA", "radioB", and "radioC" respectively.

Set the checkbox 'Internal Name' field to "checkbox".

Set the text input field 'Internal Name' to "nameField".

We're done editing our options menu! Save it in as "optionsMenuGui.gui" in your '/gui' folder right beiside "mainMenuGui.gui" (which should already be in there). Close TGB, 'cause it's time to script!

Image:OptionsMenu Save.JPG

We have to think about how we want our options menu to interact with our existing menu. Do we want our options menu to hide our main menu when it pops up, or just display over it? If our options menu is open, do we want our toggle menu key ('delete') to close both menus, or toggle out of one and then the other? These are all questions we should be asking before we start to script the menu behavior.

In this case I want my options menu to overlay on top of my main menu, and I want my menu key to be inactive while my options menu is open.

Now that we're ready to start scripting, the first thing we need to do is make sure that TGB knows about our GUI. Open the 'main.cs' in your project folder and edit it to exec your options GUI. It should look something like the following:

function initializeProject()
{
   // Load up the in game gui.
   exec("~/gui/mainScreen.gui");
   
   // load the main menu gui
   exec("~/gui/mainMenuGui.gui");
   exec("~/gui/optionsMenuGui.gui");
   
   // Exec game scripts.
   exec("./gameScripts/game.cs");
   
   // Remove the following four lines if you would like to start the game without running the
   // level builder.
   if ($runWithEditors)
   {
      toggleLevelEditor();
      return;
   }
   
   // This is where the game starts. Right now, we are just starting the first level. You will
   // want to expand this to load up a splash screen followed by a main menu depending on the
   // specific needs of your game. Most likely, a menu button will start the actual game, which
   // is where startGame should be called from.
   startGame($levelEditor::LastLevel[$currentProject]);
}

Good. Now we need to edit the behavior. As you might recall, we have all our menu commands in '/gameScripts/menuCommands.cs' which is executed in the startGame function in '/gameScripts/game.cs'.

Let's edit our toggleMenu function to ignore the command if the options menu is open:

// bind key
moveMap.bindCmd(keyboard, "delete", mainMenuGui @ ".toggleMenu();", "");

// toggle the main menu
function mainMenuGui::toggleMenu(%this)
{
   if(!%this.isAwake())
   {
      Canvas.pushDialog(%this);
   }
   // this check ensures the main window won't close if the options menu is open!
   else if(!optionsMenuGui.isAwake())
   {
      Canvas.popDialog(%this);
   }
}

If you remember, way at the beginning we set the "Options" button on the main menu to call "mainMenuGui.options()". Let's handle that call now:

// exit the game
function mainMenuGui::options(%this)
{
   // open the options menu up!
   if(!optionsMenuGui.isAwake())
   {
      // tell the options menu to get ready
      optionsMenuGui.initialize();
      
      // display the options menu
      Canvas.pushDialog(optionsMenuGui);
   }
}

You may have noticed the initialize function. That is where we will initialize all the values in the options GUI. To do this we will have to use the 'Internal Names' of all the objects we created earlier.

One way to do this would be to get the child of the main gui, and then get the child of that, like so:

// get the window by internal name
%dialogBox = optionsMenuGui.findObjectByInternalName("optionsMenuBox");
   
// get the window's child element by internal name
%radioA = %dialogBox.findObjectByInternalName("radioA");

Luckily, findObjectByInternalName can be set to automatically recurse through children to look for an object. To do this, just add an optional 'true' as a second argument, like so:

// get a windows child's child element by a recursive internal name search
%radioA = optionsMenuGui.findObjectByInternalName("radioA", true);

Ok, let's put this into practice on our initialize function:

// initialize various options menu controls
function optionsMenuGui::initialize(%this)
{   
   // get the window's child elements by internal name
   %radioA = %this.findObjectByInternalName("radioA", true);
   %radioB = %this.findObjectByInternalName("radioB", true);
   %radioC = %this.findObjectByInternalName("radioC", true);
   %checkbox = %this.findObjectByInternalName("checkbox", true);
   %nameField = %this.findObjectByInternalName("nameField", true);
   
   // set all the default values of the controls
   %radioA.setValue(true);
   %radioB.setValue(false);
   %radioC.setValue(false);
   %checkbox.setValue(true);
   %nameField.setValue("YourName");
}

This means that every time you open the options menu the first radio button will be selected, the checkbox will be checked, and the name field will say "YourName". We will discuss how to save out to a file and later recover values a little further on in this tutorial - this will do for now.

Our cancel button is easy, so let's do that now and then test what we have so far:

// cancel the changes and close the options menu
function optionsMenuGui::cancelChanges(%this)
{
   // just close the options menu and forget the changes
   Canvas.popDialog(%this);
}

Ok, now let's test our GUI to make sure everything works so far. Save your scripts, open up your project, and run it! If you press 'delete' then click "Options", you should see something similar to this:

Image:OptionsMenu Title.JPG


Make sure your options menu items all function properly. For exampe: you should only be able to select one radio button; you should not be able to click on the main menu while the options menu is open; your 'delete' key shouldn't do anything while the options menu is open; your cancel button should close the options dialog; etc. Note that no matter what you change, your settings always go back to our "initialize" settings.

The next step is to change that! It's time to set up our options menu to save out all our "options". The syntax of these variable assignments might look strange, and that's because they are. You really shouldn't use this syntax for anything other than global preferences/settings that are being saved out (exported). Here's an example of how you might name your radio button "radioA" for an export:

$options::radioA

Note that this is just an arbritrary name and has nothing to do with the 'Internal Name' of the radio button, the options menu namespace, or anything else. We are only naming it this because it is a name that we can remember and will mean something to us when we read it -and- we can use a search string to export all our options at once. Ok, I hope that made sense. Let's apply our settings!

// apply the changes and close the options menu
function optionsMenuGui::applyChanges(%this)
{
   // get the window's child elements by internal name
   %radioA = %this.findObjectByInternalName("radioA", true);
   %radioB = %this.findObjectByInternalName("radioB", true);
   %radioC = %this.findObjectByInternalName("radioC", true);
   %checkbox = %this.findObjectByInternalName("checkbox", true);
   %nameField = %this.findObjectByInternalName("nameField", true);
   
   // assign the variables to a sub-section of a prefs global
   $options::radioA = %radioA.getValue();
   $options::radioB = %radioB.getValue();
   $options::radioC = %radioC.getValue();
   $options::checkbox = %checkBox.getValue();
   $options::nameField = %nameField.getValue();
   
   // export these prefs to our optionsMenu prefs file
   export("$options::*", "~/prefs/options.cs");
   
   // close the options menu
   Canvas.popDialog(%this);
}

The export function searches for any global variable that starts with "$options::" and literally writes their values out to "/[project folder name]/prefs/options.cs". It will create the file and folder for you if they don't exist, so don't worry about that.

We're almost done now. All that's left is changing our initialize function to read from our new options file. We want to exec it and set all the fields to the global variables it assigns for us. It's that simple!

// initialize various options menu controls
function optionsMenuGui::initialize(%this)
{
   // get the window's child elements by internal name
   %radioA = %this.findObjectByInternalName("radioA", true);
   %radioB = %this.findObjectByInternalName("radioB", true);
   %radioC = %this.findObjectByInternalName("radioC", true);
   %checkbox = %this.findObjectByInternalName("checkbox", true);
   %nameField = %this.findObjectByInternalName("nameField", true);
   
   // load all our "options" vars!
   exec("~/prefs/options.cs");
   
   // set all the default values of the controls
   %radioA.setValue($options::radioA);
   %radioB.setValue($options::radioB);
   %radioC.setValue($options::radioC);
   %checkbox.setValue($options::checkbox);
   %nameField.setValue($options::nameField);
}

And you're done! Your options menu is now working properly and saving out options into a file.

Image:OptionsMenu Title.JPG

You may have noticed that this method assumes that there is already an options.cs file, so you have to either create one and ship it with your game to store your default prefs, or put a check in your initialize function that makes sure the file is there. You could do so like this:

// check if there is an options file
if(!isFile("~/prefs/options.cs"))
{
   // call a function that will write a prefs file
   %this.createDefaultPrefsFile();
}

You might also have noticed that your preferences are stored in plain text. This is perfect and probably what you would want for settings exported by an options menu since you can edit them in the menu anyway. But if you are saving sensitive game data or other things you don't want your user's to be able to screw with, you would want to compile your exported file and then delete the original version. The engine will look for the compiled version automatically when you try to exec it, so the usage is exactly the same. The difference is that your users won't be able to edit the file. Here is an example:

   // export these prefs to our optionsMenu prefs file
   export("$options::*", "~/prefs/options.cs");

   // compile "options.cs" into "options.cs.dso"
   compile("~/prefs/options.cs");

   // delete the original "options.cs"
   fileDelete("~/prefs/options.cs");

Now you execute the same as before, even though the original file doesn't exist anymore:

   exec("~/prefs/options.cs");

Thanks for reading, and congratulations on finishing this tutorial!


Back