Tutorials‎ > ‎UDK Scaleform Menu‎ > ‎

Part 3: Unrealscript and Kismet

Unrealscript

In the final part of this tutorial series, I'll go over how to access the components in the swf menu files from within Unrealscript and how to switch between our three different menu screens. We'll handle the click events for the buttons and the option steppers and I'll show how to load a list of resolutions from a configuration file and populate the option screen's resolution option stepper with those values. We'll also write out the changes we make in the Options Menu to the same configuration file so that they're saved for the next time the game launches. There is a LOT of code to go over, so let's dive in.

SFMFrontEnd.uc

Much of the functionality of our three menu screens is common to all three screens (clicking a button switches to another menu screen, handling the mouse, etc.), so we're going to start by creating a parent class for our menu screens - just like we did in Flash. Open up the Development\Src\Tutorial.sln and create a new Unrealscript file (Project -> Add New Item) called "SFMFrontEnd.uc". It should extend "GFxMoviePlayer" and be abstract (we won't actually ever create any instances of this class, just of its children classes). Think of the GFxMoviePlayer class as sort of an Unrealscript wrapper around a swf file. Since this is an abstract parent class, there won't actually be a swf file associated with it, but each of the child classes will define which swf to reference. Now create two global GFxObject variables: RootMC and CursorMC. The RootMC will reference the ActionScript "root" variable of the swf and the CursorMC will reference our MouseCursor varable "cursor" that we created in the SF_FrontEnd.as class that all our menu swfs inherit from.

class SFMFrontEnd extends GFxMoviePlayer abstract;

var GFxObject RootMC, CursorMC;

Next, we need to override the GFxMoviePlayer's "Start" function. This is the function that takes care of all the initialization for the swf file. It's all done natively in C++ code, but we need to tack on a couple of things for our own initialization. After we make the call to the super "Start" function, we need to call "Advance(0)" to initialize all the objects on the first frame of the associated swf without actually moving the play head forward. Then we can grab the "root" and "cursor" variables from the swf and assign them to our RootMC and CursorMC GFxObject variables:

function bool Start(optional bool StartPaused = false)
{
	local bool retVal;

	retVal = super.Start(StartPaused);
	Advance(0);

	RootMC = GetVariableObject("root");
	CursorMC = RootMC.GetObject("cursor");

	return retVal;
}

Now we just add a few default properties that we want all the menu screens to share:

DefaultProperties
{
	TimingMode=TM_Real
	bPauseGameWhileActive=true
	bCaptureInput=true
}

The "TimingMode" can be set to follow either real time or game time. Since the menus happen before the game even starts, we'll go ahead and use real time. We'll be adding a few functions to this class later, but for now, let's move on.

SFMFrontEndMainMenu.uc

Create a new class called SFMFrontEndMainMenu.uc and have it extend SFFrontEnd. We're going to attach the SF_MainMenu.swf file to this class and we want to add some event listener functions to the CLIK widget buttons that are in that swf to handle when they get clicked, so we need some GFxClickWidget global variables to assign them to:

class SFMFrontEndMainMenu extends SFMFrontEnd;

var GFxClikWidget NewGameBtn, LoadGameBtn, OptionsBtn, ExitGameBtn;

An Unrealscript GFxClikWidget is like a wrapper for an ActionScript CLIK component. The GFxClikWidget class extends GFxObject, which is a wrapper for the ActionScript "Object" class (the root class for all other classes). These four GFxClickWidget objects will be wrappers for the CLIK components in our swf file. I'm giving them the same names that I gave them in the Flash project just to keep things simple, but we'll still need to pull them out of the swf and assign them ourselves. Remember back in part one when we checked the "enableInitCallback" option for each of the CLIK components? I mentioned that enabling this option tells the component to call the "WidgetInitialized" function in Unrealscript after it was done with all its ActionScript initialization. Well, now it's time to write that function:

event bool WidgetInitialized(name WidgetName, name WidgetPath, GFxObject Widget)
{
	local bool bWasHandled;

	bWasHandled = false;

	switch(WidgetName)
	{
		case ('newGameBtn'):
			NewGameBtn = GFxClikWidget(Widget);
			bWasHandled = true;
			break;
		case ('loadGameBtn'):
			LoadGameBtn = GFxClikWidget(Widget);
			bWasHandled = true;
			break;
		case ('optionsBtn'):
			OptionsBtn = GFxClikWidget(Widget);
			bWasHandled = true;
			break;
		case ('exitGameBtn'):
			ExitGameBtn = GFxClikWidget(Widget);
			bWasHandled = true;
			break;
		default:
			break;
	}

	return bWasHandled;
}

This function simply checks the name of the ActionScript CLIK widget calling it and uses a switch statement to assign it to the correct GFxClikWidget Unrealscript object. The function needs to return whether or not the widget was handled, so we add a "bWasHandled" boolean variable. The next step to setting up the CLIK widget initialization is to add each GFxClikWidget variable to the WidgetBindings array in the GFxMoviePlayer:

DefaultProperties
{
	WidgetBindings.Add((WidgetName="newGameBtn",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="loadGameBtn",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="optionsBtn",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="exitGameBtn",WidgetClass=class'GFxCLIKWidget'))
}

A GFxWidgetBinding object has a name and a class. The name is the variable name within Flash and the class is just GFxCLIKWidget (or a custom child class that extends GFxCLIKWidget if you create one to add custom functionality). Again, there's a little bit more we need to do with this class, but I want to import the swf files into the Editor's Content Browser and set up the map and Kismet before we move on, so save the files, close down Visual Studio and open up the UDK Editor.

Importing SWFs

When the editor has finished launching, open up the Content Browser and click the "Import" button. Browse to the UDKGame\Flash\ScaleformMenuGFx folder and select the "SF_Library.swf" file. Remember that the Content Browser automatically assigns a package and group according to the file structure, which is why we laid things out the way we did. Accept the default values and hit OK. The swf asset, background image and cursor image assets will show up in a new package. Save this package in the "UDKGame\Content\Tutorial\ScaleformMenu" folder as ScaleformMenuGFx.upk. This will move the package out of the "NewPackages" folder into the "Tutorial\ScaleformMenu" folder. Next, import the SF_MainMenu.swf, SF_LoadMenu.swf and SF_OptionsMenu.swf files from the UDKGame\Flash\ScaleformMenuGFx\SFMFrontEnd folder. They will get organized into the SFMFrontEnd group inside the ScaleformMenuGFx package. When you're all done importing, you should have four swf assets and two image assets in the ScaleformMenuGFx package.

Kismet

Next, we need to create a map to house our front end. Create a new blank map and just build a cube right in the middle. Add a player start, do a quick "Build All" and save the map as "ScaleformMenuFrontEndMap.udk" in the "UDKGame\Content\Maps\Tutorial\ScaleformMenu" folder. This is the map you will actually launch when you start your game. If you're using nFringe, you'll want to set this map as the map to load at startup in the project properties. Next, open up the Kismet editor and place a new "Level Loaded" event and a new "Open GFx Movie" action (New Action -> GFx UI -> Open GFx Movie). Connect the "Beginning of Level" output from the "Level Loaded" node to the "In" input of the "Open GFx Movie" node. Select the Open GFx Movie node and under the "GFx Action Open Movie" section, set the "Movie Player Class" dropdown box to "SFMFrontEndMainMenu". Check the "Take Focus" and "Capture Input" options (might be redundant since we assigned the bCaptureInput variable in Unrealscript, but better to be safe :)). Open up the Content Browser, select the SF_MainMenu swf asset, switch back to the Kismet editor and click the green arrow to assign the asset to the "Movie" field.

Next, create a new "Toggle Cinematic Mode" action (New Action -> Toggle -> Toggle Cinematic Mode) and connect the "Beginning of Level" output from the "Level Loaded" node to its "Enable" input. Finally, create a new "Player" variable (New Variable -> Player -> Player) and uncheck its "All Players" option under the "Seq Var Player" section to get just Player 0. Wire the "Player 0" variable up to the "Player Owner" input from the "Open GFx Movie" node and the "Target" input from the "Toggle Cinematic Mode" node and save the map. Launch the map to make sure everything is working, but clicking the buttons won't do anything just yet. For that, we need to go back to Unrealscript, so close the editor and open up the Tutorial.sln solution again.

CLIK Event Listeners

In order for anything to happen when we click the buttons in our Main Menu screen, we need to add some event listeners to them. Event listeners are functions that get called when an "event" occurs, in this case, a button is clicked. To register an event listener function with the button, we just have to call the GFxClikWidget AddEventListener function, passing it the event name ('CLIK_click' for a "click" event) and the function to call when that event occurs. The function must have the same signature as the GFxClikWidget "EventListener" delegate, so it must take one parameter - an "EventData" object which (surprise, surprise) holds some data about the event that happened. We'll do the easiest one first, so let's start off by writing a function for the "NewGameBtn" button to launch a new game and then we'll add the event listener to call the function when the "NewGameBtn" button is clicked. Open up the SFMFrontEndMainMenu.uc class and add this function to it:

function NewGame(EventData data)
{
	if (data.mouseIndex == 0)
	{
		Close();
		ConsoleCommand("open ScaleformMenuMap");
	}
}

We first check to see that it was the left button that was clicked and not any other. Note that in the May 2012 version of the UDK, the EventData isn't yet being set up correctly, so the "mouseIndex" will always be "0" no matter which button you press, but we'll insert this code for when it does work. All we do now is "Close()" this movie down and use the GFxMoviePlayer "ConsoleCommand" function to open up our first map - in this case, the "ScaleformMenuMap".

Now that we've got our function ready, lets attach a listener to call it when we click our "NewGameBtn" button. Go back to the WidgetInitialized function and inside the switch statement case for the "newGameBtn", add this line of code just after we assign the "NewGameBtn" to the "Widget" parameter:

NewGameBtn.AddEventListener('CLIK_click', NewGame);

Now when the "NewGameBtn" button is clicked, the "NewGame" function will get called and launch the map. Next up is the "ExitGame" function, which is also very easy to implement:

function ExitGame(EventData data)
{
	if (data.mouseIndex == 0)
	{
		Close();
		ConsoleCommand("exit");
	}
}

This time, we do the same as before, but call the "exit" console command. Now add an event listener to the "ExitGameBtn" for the "ExitGame" function in the switch statement case for the "exitGameBtn" widget. Finally, we'll create two more functions for the "LoadGameBtn" and the "OptionsBtn", but we won't actually write anything inside them just yet. Name them "LoadGame" and "Options" and add event listeners for them in the WidgetInitialized function's switch statement cases respectively.

When the player clicks on the "LoadGameBtn" or the "OptionsBtn" buttons, we want to close the Main Menu screen and open up the Load Menu or Options Menu screen. Also, if the player clicks the "BackBtn" or the "CancelBtn" buttons in either of those menu screens, we want to go back to the Main Menu screen. Since this is functionality common to all of our screens, let's put a function in the parent SFMFrontEnd.uc class to handle it. Open up the SFMFrontEnd.uc class and create another global variable at the top of the class called "NextMenu" (note it MUST be global! A local variable will be garbage collected within 20 or 30 seconds or so and your menu screen will disappear!) which should be of the type "SFMFrontEnd". Also create a function called "LoadNextMenu" which takes an EventData parameter and a class parameter:

var SFMFrontEnd NextMenu;

...

function LoadNextMenu(EventData data, class<SFMFrontEnd> NextMenuClass)
{
	if (data.mouseIndex == 0)
	{
		NextMenu = new NextMenuClass;
		NextMenu.LocalPlayerOwnerIndex = LocalPlayerOwnerIndex;
		NextMenu.Start();
		Close(false);
	}
}

We'll assign the "NextMenu" variable to an instance of the class we pass in (which will be one of the child classes of SFMFrontEnd - aka, a GFxMoviePlayer object), set the LocalPlayerOwnerIndex to attach it to our player, and call the Start() function to initialize everything. Finally, we close down the current menu item with a parameter of "false" to keep it loaded in memory in case we want to come back to this screen. In order to switch between menu screens, we need to create another one (since all we have right now is our Main Menu), so create a new Unrealscript class called "SFMFrontEndLoadMenu.uc".

SFMFrontEndLoadMenu.uc

If you remember, the three game slot buttons in this swf file are disabled by default and have a label of "Empty". Eventually, we want to be able to look through our save directory for some saved game names and populate these three buttons with those games, but for now, let's start off with just making the back button take us back to the Main Menu screen. The class should extend the "SFMFrontEnd" class, declare four GFxClikWidget variables for each of the four buttons on the screen, define a WidgetInitialized function, add an event listener for each of the buttons, and add each button to the WidgetBindings array:

class SFMFrontEndLoadMenu extends SFMFrontEnd;

var GFxClikWidget SlotOneBtn, SlotTwoBtn, SlotThreeBtn, BackBtn;

event bool WidgetInitialized(name WidgetName, name WidgetPath, GFxObject Widget)
{
	local bool bWasHandled;

	bWasHandled = false;

	switch(WidgetName)
	{
		case ('slotOneBtn'):
			SlotOneBtn = GFxClikWidget(Widget);
			SlotOneBtn.AddEventListener('CLIK_click', LoadOne);
			bWasHandled = true;
			break;
		case ('slotTwoBtn'):
			SlotTwoBtn = GFxClikWidget(Widget);
			SlotTwoBtn.AddEventListener('CLIK_click', LoadTwo);
			bWasHandled = true;
			break;
		case ('slotThreeBtn'):
			SlotThreeBtn = GFxClikWidget(Widget);
			SlotThreeBtn.AddEventListener('CLIK_click', LoadThree);
			bWasHandled = true;
			break;
		case ('backBtn'):
			BackBtn = GFxClikWidget(Widget);
			BackBtn.AddEventListener('CLIK_click', BackFunction);
			break;
		default:
			break;
	}

	return bWasHandled;
}

function LoadOne(EventData data)
{
}

function LoadTwo(EventData data)
{
}

function LoadThree(EventData data)
{
}

function BackFunction(EventData data)
{
}

DefaultProperties
{
	WidgetBindings.Add((WidgetName="slotOneBtn",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="slotTwoBtn",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="slotThreeBtn",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="backBtn",WidgetClass=class'GFxCLIKWidget'))
}

We'll get to the Load functions later, but for now, let's take care of the "BackFunction" so we can switch back and forth between our menu screens. All we have to do to load the Main Menu screen when we click the "BackBtn" is place a call to our "LoadNexMenu" function that we just wrote from inside the "BackFunction" function that is listening for a click on that button. We just have to pass in the EventData object and the class of the menu we want to load next:

LoadNextMenu(data, class'SFMFrontEndMainMenu');

Back in our SFMFrontEndMainMenu, we do the same thing inside our "LoadGame" function:

LoadNextMenu(data, class'SFMFrontEndLoadMenu');

When we load a GFxMoviePlayer dynamically within our code like this, we need to assign a MovieInfo object, which is the SwfMovie asset we loaded into the content browser. In the Open GFx Movie Kismet node, we assigned this to our SF_MainMenu asset. We can do the same thing in our DefaultProperties section so that any time an instance of this class is created, the MovieInfo defaults to that asset. Add this line to the top of your DefaultProperties section in the SFMFrontEndMainMenu class:

DefaultProperties
{
	MovieInfo=SwfMovie'ScaleformMenuGFx.SFMFrontEnd.SF_MainMenu'
	...
}

Do the same for the SFMFrontEndLoadMenu class, pointing it to 'ScaleformMenuGFx.SFMFrontEnd.SF_LoadMenu'. Now launch the ScaleformMenuFrontEndMap and you should be able to click the "Load Game" button to launch the Load Menu screen and the "Back" button to switch back to the Main Menu screen. Also, if you haven't yet, test out the "New Game" and "Exit Game" buttons as well.

Before we move on to the more complicated parts of the Load Menu screen (reading in file names and setting the button labels), let's go ahead and create a class for our options menu and get the basic setup done:

class SFMFrontEndOptionsMenu extends SFMFrontEnd;

var GFxClikWidget ResolutionStepper, AliasingStepper, CancelBtn, SaveBtn;

event bool WidgetInitialized(name WidgetName, name WidgetPath, GFxObject Widget)
{
	local bool bWasHandled;

	bWasHandled = false;

	switch(WidgetName)
	{
		case ('resolutionStepper'):
			ResolutionStepper = GFxClikWidget(Widget);
			bWasHandled = true;
			break;
		case ('aliasingStepper'):
			AliasingStepper = GFxClikWidget(Widget);
			bWasHandled = true;
			break;
		case ('cancelBtn'):
			CancelBtn = GFxClikWidget(Widget);
			CancelBtn.AddEventListener('CLIK_click', CancelOptions);
			bWasHandled = true;
			break;
		case ('saveBtn'):
			SaveBtn = GFxClikWidget(Widget);
			SaveBtn.AddEventListener('CLIK_click', SaveOptions);
			bWasHandled = true;
			break;
		default:
			break;
	}

	return bWasHandled;
}

function CancelOptions(EventData data)
{
	LoadNextMenu(data, class'SFMFrontEndMainMenu');
}

function SaveOptions(EventData data)
{
	LoadNextMenu(data, class'SFMFrontEndMainMenu');
}

DefaultProperties
{
	MovieInfo=SwfMovie'ScaleformMenuGFx.SFMFrontEnd.SF_OptionsMenu'

	WidgetBindings.Add((WidgetName="resolutionStepper",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="aliasingStepper",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="cancelBtn",WidgetClass=class'GFxCLIKWidget'))
	WidgetBindings.Add((WidgetName="saveBtn",WidgetClass=class'GFxCLIKWidget'))
}

Whether the player cancels or saves from the Options Menu screen, we want to take them back to the Main Menu screen, so we'll call "LoadNextMenu" from both functions. Later, we'll add much more to the SaveOptions function though. Notice also that we're not adding any listeners to the Option Stepper widgets. Clicking the arrows on these widgets only cycles through their data provider arrays, and that's all taken care of in the ActionScript OptionStepper class, so we don't have to do anything but assign their data providers. We already set up the anti aliasing data provider in ActionScript, and we'll get to the resolution data provider a little later. Go back and add a call to "LoadNextMenu" to the SFMFrontEndMainMenu's Options function to load the SFMFrontEndOptionsMenu class to finish off the SFMFrontEndMainMenu class. If you test the game now, you should be able to navigate to all three menu screens.

You'll notice that when we navigate to a new menu screen, the mouse cursor jumps back up to the top left corner of the screen. This is because the coordinates are zero when the cursor is created in ActionScript, so let's add a little code to pass the current coordinates from the previous screen to the next. In the SFMFrontEnd class, add this global variable and these two functions:

var Vector2D MouseCoords;

...

function SetMouseCoords(GFxObject LastCursorMC)
{
	MouseCoords.X = LastCursorMC.GetFloat("x");
	MouseCoords.Y = LastCursorMC.GetFloat("y");
}

function SetCursorPosition()
{
	CursorMC.SetFloat("x", MouseCoords.X);
	CursorMC.SetFloat("y", MouseCoords.Y);
}

Before we switch to the new menu screen, we'll save the current mouse coordinates from the previous menu screen in the new menu screen's "MouseCoords" variable. Then, after the new menu screen is finished initializing, we'll set the "x" and "y" ActionScript variables on the CursorMC object. So, add this line to the LoadNextMenu function just after you assign the "NextMenu" object to a new instance of the "NextMenuClass"...:

NextMenu.SetMouseCoords(CursorMC);

and add this line in the Start function just above the return statement:

SetCursorPosition();

Now when you load the next menu screen, you might notice a little flicker as the mouse cursor moves back to where it was, but at least now it does go back. If it bothers you, you could experiment with trying to hide the cursor and show it again when the mouse moves, but you'd probably have to do at least some of that in ActionScript. Now, let's get back to reading our saved game file names and setting our game slot button labels.

SFMDLL

Along with the SaveGame files I had you download at the beginning of this tutorial series, I included a dll I wrote to search for files with a ".sav" extension in a path that you pass in. If you haven't already, you should download these files and drop them into your project now. I wrote this dll using C# and the UDKdll Template from Robert Giesecke. You can find more details about that template and how to use it in the C# DllBind Tutorial, but this is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
using System.IO;

namespace SFMDLL
{
    internal static class FileNameReader
    {
        [DllExport("GetSaveFileName", CallingConvention = CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.LPWStr)]
        public static string GetSaveFileName([MarshalAs(UnmanagedType.LPWStr)]string path)
        {
            if (Directory.Exists(path))
            {
                foreach (string file in Directory.GetFiles(path, "*.sav"))
                {
                    string saveName = file.Substring(file.LastIndexOf(@"\") + 1);

                    return saveName.Remove(saveName.LastIndexOf("."));
                }
            }

            return "Empty";
        }
    }
}

The final SFMDLL.dll should be in the Binaries\Win32\UserCode folder. Now let's create a DllBind in Unrealscript to use it. Create a new class called ScaleformMenuDLL, bind it to the SFMDLL and declare the GetSaveFileName function:

class ScaleformMenuDLL extends Object dllbind(SFMDLL);

dllimport final function string GetSaveFileName(string path);

DefaultProperties
{
}

Back in the WigitInitialized function for the SFMFrontEndLoadMenu class, add two local variables to the top and initialize them like this:

local ScaleformMenuDLL SFMdll;
local string BasePath;

...

SFMdll = new class'ScaleformMenuDLL';
BasePath = "..\\" $ class'SaveGameState'.const.SAVE_LOCATION;

The SaveGameState's "SAVE_LOCATION" is in the UDK\Saves\ScaleformMenu directory so that it doesn't have to be changed when you upgrade your UDK version. The SFMDLL.dll is one folder level deeper than our scripts, so we need to tack on one more "..\\" to the front of the "BasePath" that we'll be passing in to the GetSaveFileName function. Inside the switch case statements for each of the "Slot*Btn" buttons, just after we add the event listener, add this line:

SetLabel(SlotOneBtn, SFMdll.GetSaveFileName(BasePath $ "1\\"));

Substitute the correct GFxClickWidget button for each case and change the number at the end to match the slot number ("1\\" for SlotOneBtn, "2\\" for SlotTwoBtn and "3\\" for SlotThreeBtn). Now we need to write this "SetLabel" function:

function SetLabel(GFxClikWidget SlotBtn, string label)
{
	if (label != "Empty")
	{
		SlotBtn.SetString("label", label);
		SlotBtn.SetBool("enabled", true);
	}
}

As long as the label we get back from our SFMDLL GetSaveFileName function isn't "Empty", we'll set the label and enable the button. In the SetString and SetBool accessor functions, the first parameter is the name of the variable we want to change and the second is the value. The magic is all done in native C++ code, but these functions are available to any GFxObject variable. Now let's create the function to actually load the game when we click on the button:

function LoadSlot(EventData data, string path)
{
	if (data.mouseIndex == 0)
	{
		Close(true);
		ConsoleCommand("LoadGame " $ path);
	}
}

We're just using the ConsoleCommand function to load the game file we pass in. Now we just need to call this function from each of the "Load*" functions we wrote earlier. Inside the LoadOne, LoadTwo and LoadThree functions, place a call to the LoadSlot function, passing in the EventData object and the path to the saved game for that slot - the "1", "2" or "3" folder and the clicked button's label:

function LoadOne(EventData data)
{
	LoadSlot(data, "1\\" $ SlotOneBtn.GetString("label"));
}

function LoadTwo(EventData data)
{
	LoadSlot(data, "2\\" $ SlotTwoBtn.GetString("label"));
}

function LoadThree(EventData data)
{
	LoadSlot(data, "3\\" $ SlotThreeBtn.GetString("label"));
}

The GetString accessor function takes in the name of the variable you want to access and is available to any GFxObject variable. Now launch the game and click the "New Game" button on the Main Menu screen to start a new game. Open up the console and type "SaveGame 1\Test" to save a file called "Test.sav" in the "UDK\Saves\ScaleformMenu\1" folder. Exit the game and launch it again, this time clicking on the "Load Game" button. You should see the label "Test" on the first button, and if you click it, it will load the game you just saved. The other two buttons will get their labels from the first *.sav file they find in the "UDK\Saves\ScaleformMenu\2" and "UDK\Saves\ScaleformMenu\3" folders respectively.

SFMFrontEndOptionsMenu.uc

The last thing we'll do is make the options that we change in the Options Menu screen actually change some game settings. We first need to populate the resolution option stepper with some common video resolutions and we'll do this with the "UI" configuration files. In order to read values from a configuration file, we have to tell the class to use that file with the "config" keyword. We also use the "config" keyword to tell it which variables can be accessed by the configuration file. We need an array of strings to use for the data provider, and two integers to store the currently selected indices in both the resolution and anti aliasing option steppers. Add on "config(UI)" to the end of the first line of the SFMFrontEndOptionsMenu and add these new "config" variables below that line:

class SFMFrontEndOptionsMenu extends SFMFrontEnd config(UI);

var config array<string> ResolutionList;
var config int ResolutionIndex, AliasingIndex;

This tells the class to use the "UI" set of configuration files (DefaultUI.ini for default settings, UDKUI.ini for user settings). To learn more about how the configuration file system works, take a look at the Configuration File Guide on the UDN. The important thing is, we can assign values to any variables marked with the "config" keyword in a section labeled with the game project name and the Unrealscript class that holds the variable. When we overrode the DefaultUI.ini configuration file using the Project Suite Management Utility, we got a ScaleformMenuUI.ini file in the UDKGame\Config\Tutorial\ScaleformMenu folder, so open up this file and add this section:

[ScaleformMenu.SFMFrontEndOptionsMenu]
+ResolutionList="800x600"
+ResolutionList="1024x768"
+ResolutionList="1152x864"
+ResolutionList="1280x720"
+ResolutionList="1280x1024"
+ResolutionList="1600x1200"
+ResolutionList="1680x1050"
+ResolutionList="1920x1080"

This will add these resolutions to the "ResolutionList" string array in the SFMFrontEndOptionsMenu class. The next step is to get these resolutions into the ActionScript "resolutionStepper.dataProvider" variable. Add this function to your class:

function SetDataProvider(GFxClikWidget Widget)
{
	local byte i;
	local GFxObject DataProvider;

	DataProvider = CreateObject("scaleform.clik.data.DataProvider");

	for (i = 0; i < ResolutionList.Length; i++)
	{
		DataProvider.SetElementString(i, ResolutionList[i]);
	}

	Widget.SetObject("dataProvider", DataProvider);
}

The GFxMoviePlayer CreateObject function allows you to create an ActionScript object and wrap it in an Unrealscript GFxObject, but the class you're creating must be included in the swf file that the GFxMoviePlayer class references. Since we imported the "scaleform.clik.data.DataProvider" class to create the "aliasingStepper.dataProvider" within the SF_OptionsMenu.as class, it got included in the compiled swf. Next, we just loop through the ResolutionList array and set the DataProvider's elements with the "SetElementString" function. Finally, we assign the DataProvider to the Widget parameter's "dataProvider" ActionScript variable. Now all we have to do is call this function passing in our ResolutionStepper GFxClikWidget. Back in the WidgetInitialized function, add this line to the switch statement's 'resolutionStepper' case just after the ResolutionStepper object gets assigned:

SetDataProvider(ResolutionStepper);

Launch the game and go to the Options Menu screen and you'll see all the resolutions from the UI config file. Now you can add or remove resolutions without re-compiling the swf or event the Unrealscript code. The last step for this file is to make the "Save" button change the settings and save them back to the configuration files. Add this to the top of the SaveOptions function:

local int ASI, RSI;
local bool bUpdatedSettings;

ASI = AliasingStepper.GetInt("selectedIndex");
RSI = ResolutionStepper.GetInt("selectedIndex");
bUpdatedSettings = false;

if (AliasingIndex != ASI)
{
	AliasingIndex = ASI;
	ConsoleCommand("Scale Set MaxMultiSamples " $ AliasingStepper.GetString("selectedItem"));
	bUpdatedSettings = true;
}

if (ResolutionIndex != RSI)
{
	ResolutionIndex = RSI;
	ConsoleCommand("Setres " $ ResolutionStepper.GetString("selectedItem") $ "w");
	bUpdatedSettings = true;
}

if (bUpdatedSettings)
{
	SaveConfig();
}

When the "Save" button gets clicked, we get the currently selected index for both option steppers and if either has changed, we run a ConsoleCommand to change the appropriate setting. For more information on these and other settings and how to change them, see the System Settings Console Commands and the System Settings pages. If either of the selected indices change, we save the new index to the config file with the SaveConfig command, which saves the current values of all variables marked with the "config" keyword. The resolution and anti aliasing settings get saved in the UDKEngine.ini file automatically because of the console commands we called, so we only need to save the variables for the SFMFrontEndOptionsMenu class.

Now that we've saved the selected indices, we need to assign them back to the widgets when we load the Options Menu screen. In the WidgetInitialized function, add these lines just above the "bWasHandled = true;" lines for each of the 'resolutionStepper' and 'aliasingStepper' cases in the switch statement:

ResolutionStepper.SetInt("selectedIndex", ResolutionIndex);

...

AliasingStepper.SetInt("selectedIndex", AliasingIndex);

That was tons of code, but we're finally done! You can build on these screens by adding more options like a volume slider or a dynamic shadows checkbox, or you could change the three load game buttons to a list of saved games that the player can select from. Thanks for reading and I hope it's been easy to follow and informative. Good luck with your projects!