And...Action!
This is the second part of the Scaleform Menu tutorial where we're going to start attaching some ActionScript to our menu screens to make something interesting happen with them. You should go back and run through Part 1: Flash if you haven't already because this part builds on that one. As you probably already know, the CLIK components all have some ActionScript classes behind them that control most of how they work. Blending Flash design and ActionScript to make a cool UI can be tricky, so luckily, the Scaleform programmers have taken care of the bulk of the work. What we're going to do now is delve a little into that arena and expand that functionality for our own uses. We'll start off by creating our custom mouse cursor.
MenuCursor.as
When you launch a Scaleform swf in the UDK, you won't see a cursor following your mouse movements around. This is because the UDK doesn't use a mouse cursor, so we have to create one in our .swf file. We've already created a "CursorSymbol" MovieClip symbol in the library of our "SF_Library.fla" file in the first part of this tutorial. We attached it to a class called "lib.MenuCursor" and created this class in the Development\Flash\Tutorial\ScaleformMenu\as\lib folder (although we didn't edit it just yet). Open up this MenuCursor.as file within Flash Professional and replace the constructor with this:
public function MenuCursor(parentStage:DisplayObject)
{
mouseEnabled = false;
mouseChildren = false;
parentStage.addEventListener(MouseEvent.MOUSE_MOVE, MoveCursor);
}
We want to add the MOUSE_MOVE event listener to the stage where the cursor will be added, not to the MenuCursor itself, so we need to pass in a reference to it (the "parentStage" DisplayObject). If we tried to add the event to this MenuCursor object, it would only move when the mouse coordinates were over the cursor sprite. This is also the reason we set the "mouseEnabled" and "mouseChildren" variables to false - we want the stage to handle all the mouse events, not the MenuCursor.
We need to import the DisplayObject and MouseEvent classes, so place the text cursor at the end of the word "DisplayObject" and hit ctrl + space to bring up Flash's code hinting. Double click the DisplayObject entry in the popup to automatically add the import statement at the top of the file. Do the same thing for the MouseEvent class to import "flash.events.MouseEvent"
We added a listener function for the MouseEvent.MOUSE_MOVE event (the "MoveCursor" function), so we should write that function now:
private function MoveCursor(e:MouseEvent):void
{
x = e.stageX;
y = e.stageY;
}
All this function does is set the x and y coordinates for the MenuCursor object to the mouse's x and y coordinates stored in the event's "stageX" and "stageY" variables. That does it for the MenuCursor, now let's make use of it.
SF_FrontEnd.as
Each of our menu screens will use the MenuCursor object, so rather than write the same code over and over again for each screen, let's create a parent class called SF_FrontEnd that each of the other menu screens will inherit from. Create a new ActionScript class (File->New and choose "ActionScript 3.0 Class") and type in the "Class name" field "SF_FrontEnd". Save it in the Development\Flash\Tutorial\ScaleformMenu\as\frontend folder. Just above the constructor, create a MouseCursor variable called "cursor":
public var cursor:MenuCursor;
You'll need to import the MenuCursor object. The ctrl + space shortcut will only work if the .fla files is still open, otherwise, Flash Professional doesn't know where to look for the class. In order for each of the menu screens to use our custom menu cursor, we have to load the SF_Library.swf where the custom cursor image is located so that the MenuCursor class knows which image to use. Inside the SF_FrontEnd constructor, write these four lines:
var cursorPath:URLRequest = new URLRequest("../SF_Library.swf");
var libLoader:Loader = new Loader();
libLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, CreateCursor);
libLoader.load(cursorPath);
You'll need to import the URLRequest, Loader, and Event classes as well. We're loading the SF_Library.swf file (notice that the relative path location is actually where the final SF_Library.swf will be in relation to each of the Menu swf files, not to the SF_FrontEnd.as file) and adding an event listener for when it finishes loading. We have to wait until it finishes loading to create a MenuCursor object, otherwise, the image won't get attached to it and we won't see the cursor on the screen. Now let's create the "CreateCursor" function that gets called when the SF_Library.swf file finishes loading:
private function CreateCursor(e:Event):void
{
Mouse.hide();
cursor = new MenuCursor(stage);
stage.addChild(cursor);
}
You'll need to import "flash.ui.Mouse" for the call to "Mouse.hide()". We're hiding the default mouse cursor and adding our own custom mouse cursor to the stage. Notice that we pass in the "stage" variable to the constructor for the MouseCursor class so that we can add the MOUSE_MOVE event. Now open up the SF_MainMenu.as, SF_OptionsMenu.as and SF_LoadMenu.as files. Instead of extending "MovieClip", we now want them all to extend "SF_FrontEnd". They're all part of the same package, so we don't have to import any classes. In each of their constructors, simply place a call to "super();" to call the parent constructor. That's all for the MenuCursor. Now if you launch any of the SF_*Menu.fla files (either through Flash Professional or the Scaleform Launcher plugin), you should see our custom cursor moving around the screen.
As a side note for each of the SF_*Menu.as files, some of the Scaleform components require that you deselect the "Automatically declare stage instances" option in the Advanced ActionScript 3.0 Settings in the FLA document's Publish Settings (the window where we set the Source path to "../as/frontend" in the first part of this tutorial). For the components we're using for this tutorial, this is unnecessary, but if you do use components that require it (see this post from Mathew Doyle for more information), you'll need to declare variables for each of the objects you place on the stage in these ActionScript files.
ActionScript or Unrealscript?
Now we're at the point where we have to decide which parts of our menu screens' interaction should happen in ActionScript and what needs to go in Unrealscript. To answer this question, we should refer to the Scaleform ActionScript Best Practices Guide. Epic recommends that you leave most of the heavy lifting to Unrealscript as ActionScript is a somewhat slower language. However, making calls back and forth between your Unrealscript and ActionScript code takes time, so there are cases where you do actually want to write the code in ActionScript to avoid the overhead. For example, it's a good idea to control the interactivity from within ActionScript (like our MouseCursor class' "MoveCursor" function - trying to send that data back and forth between ActionScript and Unrealscript would be a nightmare!) Also, they recommend that animations be handled primarily on the timeline, as scripted animations tend to perform worse. If you're using Flash and not just creating the swf strictly from ActionScript, I can't imagine why you wouldn't use timeline animations anyway!
With those guidelines in mind, we'll keep our ActionScript to a minimum and do most of the work in Unrealscript. There is one piece of code though that we're going to add to the "SF_OptionsMenu.as" - the data provider for the anti-aliasing option stepper. The data provider is basically an array of options that we're going to want to step through. In this case, it will be an array of anti-aliasing multipliers. In the constructor for the SF_OptionsMenu class, add this line below the "super();" call:
aliasingStepper.dataProvider = new DataProvider(["None", "2x", "4x", "8x", "16x"]);
You'll need to import the DataProvider class (scaleform.clik.controls.DataProvider). This will give us five options for anti-aliasing. We won't actually handle updating the anti-aliasing setting in UDK from this class (though we could by adding a MouseEvent.CLICK listener to the "saveBtn" and using "ExternalInterface.call" to call a function in our Unrealscript class) because it's actually easier and faster to handle through Unrealscript, but updating what is displayed in the text field when we click the arrow button is a good example of something that should be handled in ActionScript. Luckily, the OptionStepper class already includes that functionality and we don't have to write it ourselves. We're not going to worry about the data provider for the resolution option stepper because I'm going to show you how to set that up in your configuration files instead. Something like screen resolutions is better handled via config files so that you can edit the array without needing to re-compile the swf. Since anti-aliasing multipliers are pretty much static, there shouldn't be any need to modify this array so we won't bother setting up a config file to handle it.
For this tutorial, I'm not bothering with animations or screen transitions just to save a little time, but if you do include them, you'll want to consider controlling them with functions in your document classes. Generally, it's a good idea to avoid putting too much ActionScript on your timeline frames (as mentioned by the Scaleform ActionScript Best Practices Guide), so I typically write functions in the document classes that I can then call from the timeline if I need to. I try to avoid calling any code at all from the timeline, but sometimes it can't be helped.
Extending Components (optional)
Depending on how complicated your menu screens are, you may want to extend the CLIK components to include some extra functionality. For example, you may want the "DefaultButton" to play some sort of animation when it's clicked - like fading away or "closing" somehow. If this is the case, it might be worth it to create a new class that extends "DefaultButton" and tacks on a MouseEvent.CLICK handler to trigger the animation. I won't go into detail about how to do exactly that, but I will show you how to set up a class that extends a CLIK component in Flash.
Back in the SF_Library.fla file, open up the "Library" tab and right click on the "DefaultButton" component. Select "Properties" from the context menu and change the name of the class to "lib.MenuButton". Clear out the "Base Class" field (Flash will look at the code to figure out which class is the "Base Class" and will complain if we have a class listed in this field) and click the pencil icon next to the "Class" field to open up a new script file for the MenuButton class. Save it in the Development\Flash\Tutorial\ScaleformMenu\as\lib folder as "MenuButton.as". Instead of extending the MovieClip class, we want it to extend the Scaleform Button class, so replace the class declaration with this:
public class MenuButton extends Button
You'll also have to import the "scaleform.clik.controls.Button" class. Now you can add whatever functionality you want to the button and it will show up in the component buttons placed in your menus.
We're done with the Flash/ActionScript side of things, now it's time to get into Part 3: Unrealscript and Kismet. Make sure to launch the SF_Library.fla and each of the menu screens (SF_MainMenu.fla, SF_LoadMenu.fla and SF_OptionsMenu.fla) once with the Scaleform Launcher plugin so that the swf files get created and saved in their output directories. When that's done, you can close down Flash Professional and open up your Unrealscript project (Tutorial.sln if you're using nFringe and the PSMU).