Welcome to another exciting UDK tutorial folks.
Matthew Doyle for Scaleform here once again.
This is the third tutorial on Mastering a GFx HUD in UDK.
We'll be going over the main HUD class, GFxMinimapHud.uc...
in the September 2010 UDK build.
If you've watched the previous tutorials, you'll know that this is the actual class...
that does all the HUD magic.
As before, we've got a whole lot of ground to cover.
So rather than explain every graphic HUD element,
I'm just gonna go over a few of the key elements.
You'll be able to use that knowledge to understand everything else.
So, let's get rollin.
Open up udk_hud.fla if you haven't already.
Likewise, open GFxMinimapHud.uc in your favorite editor.
Now, if you recall, GFxMinimapHud is the class that actually extends GFxMoviePlayer.
Put succinctly, this class caches references to all the HUD movie clips and text fields...
in the Flash file.
It then updates those elements as needed.
There are a considerable number of global variables in this class.
First we have a few related to the minimap.
Next we have the MessageRow data structure and several variables related to log messages.
Then we get to a whole mess of GFxObjects.
These are references to Flash elements within the udk_hud SWF file.
Following these, we have some variables that will be used to store the last known amounts...
for things like health, armor, ammo count, etc.
These are important to knowing whether a HUD element should be updated or not.
We'll skip over the first function, registerMinimapView() for now.
I'll be explaining the minimap in a future video.
For now, just understand that this function is called from Flash when the minimap is loaded.
The next two functions are used to initialize everything needed...
for the log display in the HUD.
First up is CreatMessageRow().
This function returns a GFxObject, in this case an empty movie clip.
When called, it will create a new empty movie clip...
nested inside the GFxObject LogMC using AttachMovie,
which can be used in the log displayed on screen.
The first parameter specifies which library symbol should be attached from the Flash file.
In this case, this is the export name of the LogMessage symbol.
The second parameter specifies the name for the new symbol...
(logMessage1, logMessage2, etc.)
Next is InitMessageRow().
This funtion uses the MessageRow data sctructure declared above...
to initialize a new message row.
It gets called from the next function, Init(), as we'll see a bit later.
On line 105, we call the previous function CreateMessageRow(), and store a reference...
to the attached LogMessage MovieClip in mrow.MC.
Using GetObject(), we can then cache a reference to the textField...
within that message row movie clip so that we can set its text.
Let's check out the Flash object structure.
In the library pane, open the log assets folder and double click the logMessage movie clip...
to open it.
Clicking on the object on the stage,
we can see that it has an instance name of message.
Drilling down into that movie clip, we find the actual text field,
which has an instance name of textField.
If you go back now into the HUD class file, line 107 starts to make more sense now.
Now we want the text field to allow for html style text tags,
so we use SetBool to set the html property of mrow.TF to true.
SetString is used to set the html text field to be empty of any actual text for now.
Finally, we add the new message row to our pool of available message rows,
the FreeMessages array,
before returning the newly created and configured GFxObject mrow.MC.
This brings us to the Init() function.
If you recall, this function is called by UTGFxHudWrapper.uc...
when it first instantiates the HUD.
It's core job is to cache references to all of the flash movie clips...
and text fields.
The first local variable is used in a for loop a bit further down.
The next variable has been deprecated, so you can ignore it.
Next, we call the super class, or GFxMoviePlayer, Init() function.
This is a general Object Oriented Programming practice.
There is likely to be logic in GFxMoviePlayer’s Init() function that needs to be executed.
Since we override the function, we have to either call the superclass’ version...
or copy and paste that code here.
We store the world info so that we can easily access it later,
and we need to to retrieve the game replication info.
We also get all the game data and store it in GRI.
Next comes the two GFx functions that start the movie and begin playback -
Start() and Advance() respectively.
This next little block sets the intial values for several important variables,
including the number of log messages, as well as the last health, armor, ammo, etc.
We set these to negative amounts since we're starting a new game.
Next up is a block of if statements which make use of...
that deprecated temporary GFxObject above.
You can ignore all these if statements completely as such.
For the sake of explanation though, I'll tell you what these do.
Essentially, they were used to hide movie clips that we don't want visible...
when the game starts.
We store a movie clip from the Flash file inside the temporary widget,
using GetVariableObject().
We then test to see if our GetVariableObject() returned a valid reference.
If it does, we use SetBool to set the visiblity of that object to false.
If it didn't, then the MovieClip did not exist and there’s no need to hide it.
None of these movie clips exist in the current HUD Flash file though.
Moving on we cache a reference to the log movie clip from the root of the timeline in Flash.
In Flash, we see on the log layer that we have an empty movie clip...
with an instance name of log.
We then run a for loop that executes the InitMessageRow() function above,
giving us a total of 15 new movie clips containing empty html text fields for the message log.
We then cache references to all the various movie clips and text fields in the Flash file.
We want to cache references to these items once to avoid retrieving them...
everytime we need to access or modify one,
which incurs a small performance penalty.
As an example, let's look at the health text field and movie clip.
We cache _root.playerStats.healthN in HealthTF using GetVariableObject().
HealthBarMC contains _root.playerStats.health.
Let's head into the Flash file now.
We start out in the root of the hierarchy (_root).
If you click on the bottom right movie clip, you'll see it has an instance name of playerStats.
This gives us _root.playerStats.
Now drill down into playerStats by double-clicking it.
Inside this movie clip, unlock the healthN layer and click on the text field on the stage.
You'll see it has an instance name of healthN, giving us a hierarchy of _root.playerStats.healthN,
just as we've cached it in UnrealScript.
Now, unlock the health layer and click on the health bar graphic.
It has an instance name of health, giving us a hieararchy of _root.playerStats.health.
Alright, back in UnrealScript.
Here we have a block that caches the team based movie clips.
Note that if the game is not a team game, we set several of these movie clips to be hidden...
with the SetVisible property of that object.
The various directional hit indicator movie clips are stored as elements of the HitLocMC array,
starting from the top indicator and moving around clockwise.
After we've cached all these references, we then do some 3D transforms on several of them.
ActionScript 2 does not distinguish between floating point and integer numbers.
So we use SetFloat to send the _yrotation value to each of the movie clips,
rotating them slightly on the y axis in 3D space.
Last of all, the Init function calls ClearStats() with a parameter of true.
Let's skip over the next function, which simply formats time...
into an hours, minutes, seconds format...
to explain ClearStats().
ClearStats()'s job is simply to set the various HUD movie clips and text fields...
to a new game state.
The first three lines create an ASDisplayInfo instance...
that will be used used to set the X size (or width) of graphical status bars.
We tell DI that it has XScale, then set the XScale value to 0% of its maximum size.
Let's take a closer look at a few of these if statements.
This second if statement checks to see if LastHealth is currently something other than...
the initialized value of -10.
If it is, we set the health text field to empty using SetString on the text property...
of the cached reference to the text field.
We also set the health bar movie clip, which, as you have seen, is a solid green bar,
to have the display info properties we stored in DI above.
This effectively sets the health bar graphic to 0% in width, rendering it hidden.
Finally, we set LastHealth to -10 to represent a new game state.
We do something similar for LastAmmoCount, but in this case, there is no graphical bar.
Let's have a quick look at the ammo indicator in Flash.
We can see that the ammo indicator uses a keyframed animation,
with each keyframe showing a different amount of ammo.
So, back in GFxMinimapHud, we tell the ammo bar movie clip to GotoandStopI() on frame 51,
which essentially tells it to display an empty frame with no graphic.
The I in GotoAndStopI() stands for integer, as we're passing a frame number as an integer.
You can also use GotoandStop() without the I.
In that case, you would have to pass a string value...
corresponding to a frame label in Flash.
And, looking at LastWeapon, we see that we can also...
hide a movie clip using SetVisible(false).
Ok, moving right along to our next function.
Put succinctly, AddMessage() is used to display log messages.
This function gets called from AddDeathMessage() below,
as well as from LocalizedMessage() in UTGFxHudWrapper.
It expects two parameters -
the type of message and the message itself, both as strings.
We want every new log message to push older messages up the screen by a specific amount.
And we'll use an ASDisplayInfo instance to change...
the x-y coordinates of log messages already being displayed...
to make space for the new message.
If the message has no length to it, then there’s nothing to display so we can safely return.
Now if there are any currently unused log messages available in our FreeMessages pool,
we can use those rather than create a new message.
To do so, we simply pop one off the end of the FreeMessages list and continue with it.
But, if there are no currently available message rows,
we’ll use the oldest log message row...
that is currently being displayed.
Then we set the String to be displayed in the mrow textField...
using SetString as well as what type of message it is.
The new log message will have a Y value of 0.
This y-coordinate will be relative to the MovieClip that it is attached to.
In this case, this will cause new messages to be displayed at the bottom...
of the log movie clip container.
We tell the log messages movie clip to goto and play "show", effectively displaying it.
Looking back in the Flash file, we can see that the log message...
is fully displayed, starting at the "show" frame...
for a few seconds before finally fading out.
After that, we use a for loop to set the Y value of each log message...
so that all the older log messages move up by the amount specified in MessageHeight.
Notice that we can reuse the same ASDisplayInfo instance...
for each SetDisplayInfo() call by simply modifying...
its Y property rather than creating a new ASDisplayInfo for each call.
You'll find the value for MessageHeight in the default properites,
and it happens to be set to 38 pixels.
Last of all, we insert the new log message row into the pool of log messages that are...
currently being displayed.
Alright, so that's going to do it for this video.
We'll have to pick up on the next video with UpdateGameHud().
We'll continue it there, and we should finish up with GFxMinimap Hud in that video.
Until then, this is Matthew Doyle for Scaleform, as always, signing off.