Sunday, June 26, 2011

D&D BattleMap Using the UDK – Transitions, Menus and Config Files

The UI for our placeable object commands isn’t quite ready yet (it seems everyone’s suddenly a critic), but this post we’ll introduce similar concepts while implementing another desperately needed feature: transitioning between maps.  I’m getting a little tired of having to close down the UDK and reconfiguring the Unreal FrontEnd just to change maps:



We’ll provide the option to choose a map with a flyout menu built in Flash.  But to achieve our objective of keeping the maps independent of the BattleMap mod, we can’t hard code the map list into the menu.  Therefore, we’ll store the list in a config file and pass it to the HUD on startup.

Flash

Once again, our friends at MonsterLayer.com produced some gorgeous assets for us in an astonishingly short amount of time.  I’ll do my best to break down how they built our menu, but you can certainly create any style of menu that suits you.  All that’s important is that the asset names match the code.

Below is a snapshot of an individual menu button.  This will be duplicated multiple times: one for each map.

The “Scripts” layer simply contains:
stop();
The second frame of the background later changes the color.  That will become the “hover” when moused over.  (Yes, Flash does include a Button type.  However, we had multiple issues trying to get it to work correctly so we went with the programmer’s approach of just coding it ourselves.)

Make sure the Text object is named “ButtonText” and save the symbol as a MovieClip named “MapButton”.

Create another button the exact same way:

Name this MovieClip “CloseButton”.

Next, create a menu that contains your button objects:

Name the map button instances “MapButton0”, “MapButton1”, etc.  Name the close button instance “CloseButton”.  Save this MovieClip as “MapList”.

Create another MovieClip called “MapListMenu”.  Add an instance of MapList named “MapListInst”.  Create a motion tween of it moving onto the stage:

The scripts on the 1st and last frames both simply contain:
stop();
Now, add an instance of MapListMenu to your root movie named “MapListMenuInst”.  Position it so that it starts outside the scene, but ends inside at the end of its animation.  (Edit In Place comes in real handy here.)

We need one more object.  Transitioning between maps can take a few seconds to initiate.  We should provide a visual clue that the transition has begun and the UDK isn’t hung.  Create an object named “Overlay”.  I drew a giant semi-transparent background with the ever-polite instructions “Please Wait…”:

Add an instance of your Overlay object to your root movie as “OverlayInst”.

ActionScript

Now, we need to wire it all up.  Add the following to your root ActionScript.  It initializes an array to store the map list, hides the Overlay and sets up mouse events for the buttons.  The click event for the map buttons will show the Overlay on top, toggle off the menu and pass the chosen map back to UnrealScript:
var Maps:Array;
// Hide overlay
OverlayInst._visible = false;

// Init map buttons
i=0;
while(MapListMenuInst.MapListInst["MapButton"+i])
{
 MapListMenuInst.MapListInst["MapButton"+i]._visible = false;
 MapListMenuInst.MapListInst["MapButton"+i].onRollOver = function(){
  this.gotoAndStop(2);
 }
 MapListMenuInst.MapListInst["MapButton"+i].onRollOut = function(){
  this.gotoAndStop(1);
 }
 MapListMenuInst.MapListInst["MapButton"+i].onRelease = function(){
  OverlayInst.swapDepths(_root.getNextHighestDepth());
  OverlayInst._visible = true;
  ShowMapList();
  ExternalInterface.call( "OpenMap", Maps[this._name.substr(9)]["File"] );
 }
 i++;
}

// Init close button
MapListMenuInst.MapListInst.CloseButton.onRollOver = function(){
 this.gotoAndStop(2);
}
MapListMenuInst.MapListInst.CloseButton.onRollOut = function(){
 this.gotoAndStop(1);
}
MapListMenuInst.MapListInst.CloseButton.onRelease = function(){
 ExternalInterface.call( "CloseMap" );
}
Next, add the following InitMapList() function.  This will be called by UnrealScript to pass in the map list and setup the map buttons:
function InitMapList(Param1:Array)
{
 Maps = Param1;

 //Show only buttons with maps
 for (i=0; i<Maps.length; i++)
 {
  MapListMenuInst.MapListInst["MapButton"+i].ButtonText.text = Maps[i]["Name"];
  MapListMenuInst.MapListInst["MapButton"+i]._visible = true;
 }
}
Finally, add the ShowMapList() function to toggle the menu on/off:
function ShowMapList()
{
 if (MapListMenuInst._currentframe == 1)
 {
  MapListMenuInst.swapDepths(_root.getNextHighestDepth());
  CursorInst.swapDepths(_root.getNextHighestDepth());
  MapListMenuInst.gotoAndPlay(2);
 }
 else
  MapListMenuInst.gotoAndStop(1);
}
Republish the .swf file then open up the UDK, find the BattleMapHud package, and reimport BMHud.

BattleMapConfig.uc

Now, let’s setup a config file to store the map names.  The UDK builds in configuration file functionality into the base Object class.   That means that every class can implement its own config file.  Mougli’s portfolio includes a very good tutorial on UDK configuration files.  To sum up, any variable declared globally in a class becomes an entry in its config file.

Here’s the code for a simple object containing an array of MapItem structs:
class BattleMapConfig extends Object config(BattleMap);
struct MapItem
{
 var config string Name;
 var config string File;
};

var config array <MapItem> Maps;
The “config(BattleMap)” directive tells the UDK that this object will read and write to a BattleMap.ini file.

DefaultBattleMap.ini

In your /UDKGame/Config directory, create a new .ini file called “DefaultBattleMap.ini” and enter the titles and file names of your maps.  For example, mine looks like:
[BattleMap.BattleMapConfig]
Maps=(Name="Demon Queen's Enclave U1, L1",File="BM_DemonQueensEnclave.udk")
Maps=(Name="Demon Queen's Enclave L13-16",File="BM_DemonQueensEnclave1.udk")
Maps=(Name="Demon Queen's Enclave L2-6,11,V1",File="BM_DemonQueensEnclave2.udk")
Maps=(Name="Demon Queen's Enclave V2,V4-V10",File="BM_DemonQueensEnclave3.udk")
Maps=(Name="Demon Queen's Enclave V12",File="BM_DemonQueensEnclave4.udk")
The format should look very familiar, it’s similar to DefaultInput.ini where we include new key bindings.  Notice that the section heading is the name of our class.

DefaultInput.ini

Speaking of DefaultInput.ini, while we’re here go ahead and add a key binding to toggle our menu:
-Bindings=(Name="Escape",Command="GBA_ShowMenu")
.Bindings=(Name="Escape",Command="BMShowMapList")
(In case you’re curious, the period in front of .Bindings means that duplicate entries are allowed.)

BattleMapPlayerController.uc

Here, we simply need to instantiate our new BattleMapConfig class, which will cause it to automatically initialize its variables from the config file:
var BattleMapConfig BMConfig;
simulated function PostBeginPlay()
{
 super.PostBeginPlay();
 BMConfig = new class'BattleMapConfig';
}
BattleMapHUD.uc

Inside PostBeginPlay(), add a call to a CallInitMapList() function right after the CrosshairMovie.Initialize() statement and pass in the newly loaded Map array:
  CrosshairMovie.CallInitMapList(BattleMapPlayerController(PlayerOwner).BMConfig.Maps);
BattleMapPlayerInput.uc

Create a new command to tell the HUD to toggle the menu and another to tell the UDK to load a new map:
exec function BMShowMapList()
{
 if (WorldInfo.NetMode == NM_Standalone || WorldInfo.NetMode == NM_ListenServer)
 {
  BattleMapHUD(myHUD).CrossHairMovie.CallShowMapList();
 }
}

exec function BMOpenMap(string MapFile)
{
 WorldInfo.Game.ProcessServerTravel(MapFile);
}
BattleMapGfxHud.uc

First, create two new wrapper functions for calling the HUD’s InitMapList() and ShowMapList():
function CallInitMapList( array <MapItem> Param1 )
{
 ActionScriptVoid("InitMapList");
}

function CallShowMapList()
{
 ActionScriptVoid("ShowMapList");
}
Finally, create two receiver functions called by the HUD to execute our BMOpenMap() command and the Quit command:
function OpenMap(string MapFile)
{
 ConsoleCommand("BMOpenMap " @ MapFile);
}

function CloseMap()
{
 ConsoleCommand("Quit");
}
That’s it!  If everything went smoothly, hitting Esc will now pop up a menu of map choices.  Clicking on a map name will transition to a new map.  Clicking the close button will cleanly exit the UDK.

BattleMap mode source files

Thursday, June 9, 2011

Taking a Second Wind

There won't be a blog post addressing a new feature this coming weekend.  But I didn't want to leave you without something to play with, so I'm addressing a few questions asked in the comments:

Our friends at MonsterLayer.com gave us permission to give away the assets they created for us.  Copy BattleMapItems.upk to your /UDKGame/Content/Misc directory.  It includes:
  • Bed
  • Bedroll
  • Chair
  • Chest
  • Drawer
  • Ladder
  • Small Table
  • Table
Here's a couple of clips of the BattleMap in use from our last session:


And finally, according to the UDK Licensing FAQ, as long as you're not making any money off your creation you are free to package and distribute your project.