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

14 comments:

  1. Cool Stuff. Thanks ZomBPir8Ninja and apologies for any requests/feedback, certainly not aiming to criticise. Your doing some fantastic work.

    I have built my menu and buttons (I chose to do the graphics in PS and then import the constituent components into Flash). I think I have done a pretty good job of mimicking the style MonsterLayer.com provided you.

    All is working as expected with the exception that the text on the MapButtons is displayed as a series of [] for example [][][][][][][]. What's odd is that I used the same font thats already embedded in the SWF (used for token text) and the Close Button. The text on these objects is fine, just the MapButtons text that looks to be using non-available characters. The buttons are all built the same, close button text is displayed but mapbutton text is not. All button text objects are named as ButtonText.

    Any ideas what I have done wrong or could be causing the issue?

    Aside from that the menu works really well and levels load/UDK exits when the buttons are clicked on.

    ReplyDelete
  2. @Dr Zeuss: Oh, that was a friendly jab at my group for suddenly having opinions about how a feature should look and behave.

    As for your Flash font issue, a couple of things to check:

    * Make sure your embedded font is including all the glyphs you're using: Right click...Properties...Options...Character Ranges.
    * Make sure ButtonText is using Dynamic Text and triple-check the font. (If you click the Embed button, you should see the same glyphs already checked as above.)
    * trace() the text you're passing in, to make sure it's what you expect instead of a garbled string
    * trace() the result of ButtonText.text after you set the value to make sure it's what you expect.

    ReplyDelete
  3. @ZomBPir8Ninja: My bad:

    As for the Flash font issue, a trace() revealed the problem was indeed down to the embedded font not including all the required glyphs (un-suppressing the DevGFxUI package reported a sleuth of missing font messages in Launch.log). I couldn't figure out which glyphs it required as I already had Capital and Lowercase alphas, numericals, punctuation and Latin enabled so I ended up ticking All in the end. It's all working now with all buttons showing all text.

    One more question. :)

    When I click on a menu button, I briefly see the Please Wait overlay but its quickly replaced with a standard UDK level splash page with spinning Unreal logo containing the name of the map being loaded. Is that expected behaviour or should my Please Wait Overlay be displayed until the new map is fully loaded?

    ReplyDelete
  4. @DrZeuss: Yes, the Please Wait overlay is simply a temporary display until the UDK level change kicks in. As you can see from the video, my UDK takes several long seconds before the level changes.

    ReplyDelete
  5. @ZomBPir8Ninja: D'oh! I should have thought of watching the video. What a plumb.

    The delay between the Please Wait overlay and the UDK splash loading page might be related to the size and complexity of the map, that might explain why it takes longer on your setup as I have only been using small 1-2 room sized maps.

    I assume the UDK loading page is another SWF animation so was wondering if it too could be swapped out for a custom one. I assume there's a config setting somewhere that allows you to override/edit the SWF object to use when levels are loading?

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. OK, I figured out how to replace the the map loading screen. The video is actually a BINK compressed video (.bik file) not a SWF as I had earlier suspected.

    Whilst I have successfully replaced the loading screen in my UDK setup (I used PS and the RAD Video Tool to produce my BINK video), I think I have violated the UDK EULA as the terms only seem to allow for the splash page to be changed but not any of the BINK videos. Therefore I am reticent to provide the assets I created. I did create a couple of quick videos to show the change off, however I have now removed the posts (see above) as I do not want to incur the wrath of Epic's legal team.

    For those who want to do something similar (at your own risk), you need to add a .bik file to the UDKGame/Movies folder and then edit the FullScreenMovie entries in DefaultEngine.ini to point to your new .bik files.

    ReplyDelete
  9. A quick note to say I took the plunge and updated to the UDK June Beta and am happy to report that the BattleMap still works. I haven't found any glitches yet but I'll be sure to let you know if I find any.

    Also, I found an excellent liitle .NET tool called UDK Launchpad which provides a very easy to use but powerful tool for launching UDK games.

    You can find it at http://forums.epicgames.com/showthread.php?p=28042919.

    ReplyDelete
  10. Are you going to be releasing this thing when it's ready for those of us with limited programming skills? I mean I LOVE the idea and I like 3D modelling and map making, but I'm very bad at programming so is it ever going to be in distribution?

    Also I'm thinking of running this on a tipped down LCD TV 'cause I don't have any projector and with TV you don't have to concern yourself with the lighting of the room :P

    ReplyDelete
  11. "Are you planning to be releasing this when it's ready for those of us with limited programming skills? I mean I LOVE the idea and I like 3D modelling and map making, but I'm very bad at programming so is it ever going to be in distribution?"

    I second this. I love your work. I am a decent programmer however I cannot get the base started.

    Thank you,
    bennyty

    ReplyDelete
  12. Are there any more updates on this project? Its been a couple of months since the last update.

    ReplyDelete