HUD & Canvas Intro
The HUD of a game is what defines what flat on-screen features are visible on a screen. These features are used to display important information to the player. For example:
In this case, the HUD contains a number of dynamic elements and static elements.
- The text in the middle of the screen, the team score, and the remaining time are all dynamic text elements
- The current weapon image is static as long as the weapon does not change, ie it changes when the player changes weapon
- The ammo stock (the bullets) reflect how much ammo is in the weapon where grey bullet icons reflect the lack of a bullet and white/red bullets means remaining ammo. If the weapon's ammo is greater than 32, the excess ammo is not displayed
- The player's health is the combination of a transparency mask and a green square texture that is scaled from the left side to the right side
UDK HUD Implementations
There are two ways to build a UI using the UDK.
- Scaleform. This is a technology that backbones on Adobe flash. Although this is an incredibly useful and flexible technology, we will not be discussing it as it requires a level of familiarity with Flash and ActionScript (AS3 to be specific). If you are comfortable with these technologies and would like to explore Scaleform for your project, please feel free to do so; if you wanted to go down this road, you should start by reading the Scaleform quickstart in the Scaleform part of the UDN available here.
- Canvas. This is a way to programmatically add 2D assets to the screen. This method is what we will be discussing in this course as it requires no prior expertise and is simple to hook into game logic.
Attaching a HUD to a game type
In order to attach a HUD to a game type and have that HUD be used whenever that game type is used, we set the default property HUDType to be the HUD class itself. For example, this is how the HUD is set in the UTGame class.
HUDType=class'UTGame.UTHUD'
Now if your game type did NOT inherit from UTGame somewhere in the inheritance chain, you would be done. However, if it DOES inherit from UTGame at some point, then you additionally need to set the default property bUseClassicHUD to true. The variable bUseClassicHUD is defined in the class UTGame:
/** Whether to use Classic UTHud */
var bool bUseClassicHUD;
To see why that is, take a look at the following function found in the class UTGame:
/** handles all player initialization that is shared between the travel methods
* (i.e. called from both PostLogin() and HandleSeamlessTravelPlayer())
*/
function GenericPlayerInitialization(Controller C)
{
if ( !bUseClassicHUD )
{
HUDType = bTeamGame ? class'UTGFxTeamHUDWrapper' : class'UTGFxHudWrapper';
}
super.GenericPlayerInitialization(C);
}
Notice that if bUseClassicHUD is false (which it is by default), the player's HUDType is overridden to be one of class'UTGFxTeamHUDWrapper' or class'UTGFxHudWrapper', regardless of what the value of HUDType happens to be in the default properties of the game type!
HUD Class
The HUD class itself is not a particularly complex class, but it does contain just enough that you can get a lot done. It has two important members:
- The DrawHUD function. This member function returns nothing and receives no arguments. This is the function that is called every frame (tick) to actually draw the elements of the HUD. This is where your HUD drawing code should reside.
- The Canvas member variable. This is a reference to a Canvas class instance that can be used to actually draw onto the screen.
- The PlayerOwner member variable. This is a reference to the PlayerController object that owns this HUD. Defined as follows:
var PlayerController PlayerOwner; // always the actual owner
Use this to gain access to the player controlling the HUD.
Additionally, the HUD class itself has a few functions that can be used to make drawing some objects simpler; we will discuss those further on in this article.
Drawing with Canvas
The Canvas class represents a stateful drawing surface. That is to say, you should imagine this class like a machine with many levers. You set the levers to desired positions indicating your preferences for later actions then press the execute button performing an action. Let's look at some of the most useful properties that could be set.
As an analogy, imagine that you had to design a system that draws boxes on a screen; for this example, we can assume that the edges of these boxes will be parallel to their matching screen edges. So, the minimal set of information that you would need to draw a box would be:
- How far up the screen the top edge is
- How far up the screen the bottom edge is
- How far from the left of the screen the left edge is
- How far from the left of the screen the right edge is
In this case, you would assume that the coordinate for the top edge is greater than the coordinate for the bottom edge. Similarly for the right and left edge. Typically in GUI systems, this information is stored in the form of the top left corner and the bottom right corner.
The simplest way that we could imagine this being put together is as follows:
A single function accepting four parameters that actually draws the rectangle. Eg: DrawRect(float top, float left, float bottom, float right);
Alternatively: DrawRect(float top, float left, float width, float height);
- Advantages:
- Simplicity, as the correlation between the code and the function performed is intuitive
- Disadvantages:
- Adding parameters with future revisions means either changing the function signature or adding more and more function overloads
- Repeated drawing calls that use the same top left corner must resupply the top left parameter on every call
Instead of going down this road, the UDK takes some of those parameters and rips them out into their own functions. Those functions then set state on the Canvas object which drawing functions use when actually drawing. The above analogy is instead implemented as follows:
SetPosition(float left, float top);
DrawRect(float width, float height);
Instead of passing both top-left along with width-height, we separate out the two concepts. This means we can now do code like this:
SetPosition(100, 200);
DrawRect(50, 50);
DrawRect(100, 100);
DrawRect(200, 200);
Notice how we didn't have to resupply the top left position on every draw call. This is what it means for an object to be stateful.
Note that Canvas has a wide array of functionality and we will not be able to discuss all of it. Please take a look at the canvas technical guide on UDN to learn more.
Canvas Properties
The most important properties of the Canvas object are its position, colour, and size.
Position
This is typically the top left position of the next drawing call. This is set by calling SetPos: SetPos(float x, float y, float z = 1.0);
In this case, x and y are pixel coordinates where the top left corner of the next draw should be performed. Note that based on the draw call itself, it is possible that this position may not be treated as the top-left corner for that particular draw call (eg when drawing centered text). Notice that the z parameter has a default value; therefore, we typically omit passing a value for the z parameter.
Colour
This is the colour of the next draw call (if the next draw call uses a colour). Colours are represented by four integer values ranging from 0 to 255 where 0 is no contribution and 255 is maximum contribution:
- Red. This is the contribution of the red channel.
- Green. This is the contribution of the green channel.
- Blue. This is the contribution of the blue channel.
- Alpha. This is how transparent you want the next draw to be where 0 is completely transparent and 255 is completely opaque.
Colour is set by calling SetDrawColor or SetDrawColorStruct. SetDrawColor: SetDrawColor(byte R, byte G, byte B, optional byte A = 255);
SetDrawColorStruct is just a wrapper for SetDrawColor that accepts a Color struct object.
/** Set the draw color using a color struct */
final function SetDrawColorStruct(color C)
{
SetDrawColor(C.R, C.G, C.B, C.A);
}
Note that a Color struct has four integer members, R, G, B, and A that function similarly to what is described above. BE CAREFUL that numeric variables in the UDK are initialized to 0 which implies the following:
function myFunc()
{
local Color myColor;
myColor.R = 255;
}
In this simple function we are describing a color object with Red set to 255 while blue, green, and ALPHA are all set to zero. When writing code like this, make sure that you explicitly set the alpha value of your to colour struct to something other than 0!
Size
The size of the screen in pixels can be retrieved from the Canvas object. This is useful when you need to draw objects to the screen in proportion to the size of the screen as opposed to in absolute pixel dimensions.
Retrieve the canvas members using the SizeX and SizeY members. These represent the dimensions of the screen in pixels, with 0,0 being the top-left corner. These are defined in Canvas.uc as:
var const int SizeX, SizeY; // Zero-based actual dimensions.
HUD Utility Functions, Draw*Line
The HUD class itself provides two useful functions that you may choose to use. They are Draw3DLine and Draw2DLine. They are defined in HUD.uc as follows:
// Draw3DLine - draw line in world space.
native final function Draw3DLine(vector Start, vector End, color LineColor);
native final function Draw2DLine(int X1, int Y1, int X2, int Y2, color LineColor);
Notice that unlike Canvas' draw functions, these draw functions accept a color parameter. This is because, unlike Canvas, the HUD class does not hold any colour state and these are merely utility functions.
Use Draw3DLine when you need to draw a line in the map world given two world locations as a starting point and an ending point.
Use Draw2DLine when you need to draw a line on the screen given pixel coordinate starting and ending points.