Create a Player Health Status Indicator for the Unity GUI, part 2
Posted on Oct 16, 2012 (last modified Jun 1, 2021)
This is the second of a multi-part series that will teach you how to build a player health status indicator for the Unity GUI.
Introduction
In the first part of this series, we provided the assets and a few tips for getting results fast. If you haven’t already, you should read Part 1 and also download the starter assets.
In today’s post, we’ll examine the Javascript that can be applied to any Unity GameObject to create the Health Status Indicator in your game’s GUI. The goal for today’s post is to learn how the script works and to hopefully learn a little bit about Javascript programming in Unity.
You’ve got an awesome game to make, so let’s get on with it…
First, let’s review
Our Javascript for the Health Status Indicator is very simple at this point, but it’s going to get a bit more complex. For that reason, it’s very important that you learn about this in phases. Let’s start by taking a look at the full script so that we have the whole thing in mind and then we’ll break it down line by line.
#pragma strict
// JavaScript
var backgroundTexture : Texture;
var foregroundTexture : Texture;
var frameTexture : Texture;
var healthWidth: int = 199;
var healthHeight: int = 30;
var healthMarginLeft: int = 41;
var healthMarginTop: int = 38;
var frameWidth : int = 266;
var frameHeight: int = 65;
var frameMarginLeft : int = 10;
var frameMarginTop: int = 10;
function OnGUI () {
GUI.DrawTexture( Rect(frameMarginLeft,frameMarginTop, frameMarginLeft + frameWidth, frameMarginTop + frameHeight), backgroundTexture, ScaleMode.ScaleToFit, true, 0 );
GUI.DrawTexture( Rect(healthMarginLeft,healthMarginTop,healthWidth + healthMarginLeft, healthHeight), foregroundTexture, ScaleMode.ScaleAndCrop, true, 0 );
GUI.DrawTexture( Rect(frameMarginLeft,frameMarginTop, frameMarginLeft + frameWidth,frameMarginTop + frameHeight), frameTexture, ScaleMode.ScaleToFit, true, 0 );
}
UnityScript is very much, but not exactly like JavaScript
Before we get too far into this, you should be aware that the script language in Unity is very often referred to as ‘JavaScript’. It is also called UnityScript and while it is very much like JavaScript, it does have some differences.
If you are familiar with JavaScript for the Web, but not UnityScript, then you may want to read UnityScript versus JavaScript in the UnifyWiki. If you’re starting to learn programming by using Unity, then don’t worry about it. Programming concepts are similar across all languages and by learning any one language, you’ll be better prepared to learn another.
Use #pragma strict
The first line in our program, #pragma strict, is very important to understand because it relates to performance and performance is extremely important in games. As a general rule, you should put that line atop all of your Javascript files in Unity, even though it isn’t required. Here’s why:
When you create a variable in most programming languages, you need to tell the compiler what kind of object or datatype the variable represents. Is it a string? A number? A texture? The compiler usually needs to know. But in JavaScript, you do not have to specify the type of a variable. When you must specify the type of a variable, it is known as strict typing. When you do not have to specify the type of variable it is known as dynamic typing because the type is figured out dynamically at runtime.
Dynamically typed variable declaration
var healthWidth = 199;
Statically typed variable declaration
var healthWidth: int = 199;
See the difference there? In the dynamically typed example, the variable healthWidth is a number, but it could also be used for a string or a boolean or some other type because we didn’t specify the type. In the statically typed example, we declared the variable as an int (a.k.a. an integer, which is just simple kind of number).
The problem with using dynamically typed variable declarations is that you’re forcing the compiler to make additional calculations while your game is running. The engine has to ask questions. Does this work as a string? Nope. Let’s try a boolean. Nope. An int? All this additional evaluation takes time, which can reduce the performance of your game.
By using #pragma strict
at the top of a script, we are telling Unity to disable dynamic typing in that script, which forces us to declare the type of each variable we create. If a type is not known, Unity will then report compile errors. This will not only improve the performance of your game, it will also make you a better programmer. So, as a rule of thumb, just do it.
Comments in your code
The second line of our script starts with two forward-slashes, which indicates that it’s just a comment.
// JavaScript
A comment is just a note inside of your program that helps you and other developers understand something. Humans can read comments in the code, but the engine will just ignore them. In this case, we’re just throwing in a note that the format of the script is JavaScript and not some other language that Unity also supports such as C#.
Two forward slashes are for single-line comments. If you want to have multiple lines of commentary in a single block, you can use the following:
/* This is an example
of a multi-line comment.
Are we having fun yet? */
Global variables and the Unity Inspector
The next three lines of the code define three global variables, each of the type Texture.
var backgroundTexture : Texture;
var foregroundTexture : Texture;
var frameTexture : Texture;
Global variables are usually declared at the very top of a script and not inside of any function. This makes them available for access from anywhere in the script. Where a variable is available for access is known as the variable’s scope, so variables outside of any function are said to have global scope.
You see, if a variable is declared first inside of a function you can’t get to it from any place outside of that function. Such a variable is said to be scoped to the function or private to the function.
Globally scoped variables show up in the Inspector panel as values that can be populated or modified without changing the script’s code. In the image below, you can see that all of the global variables in our script at right are also exposed in the Unity Inspector panel at left.
Our Health Status Indicator has already shown us two reasons why this is valuable. First, we are able to drag and drop our own textures into the Inspector slots created by the variables backgroundTexture, foregroundTexture, and frameTexture. That makes our script reusable and easy to customize. That is to say, you can reuse it for more than one game without changing any code. All you need to do is provide your own images to the widget by dragging and dropping them onto their respective slots in the inspector.
Second, we are able to drag left and right on the healthWidth slot in the Inspector while the game is running and when we do this, we can see the green health status bar reducing or increasing in size. So, the Inspector allows us to manipulate the values of global variables at runtime. Very cool!
Texture image size settings
The next two lines in our script specify the default width and the height of the green health bar image.
var healthWidth: int = 199;
var healthHeight: int = 30;
Later in the script, we use these numbers to establish a size for the rectangle that we render the texture on. We got those pixel dimensions from the drawing program we used when we saved the file. When a file is an image, you can usually get its dimensions just by inspecting the properties of the file. In Windows, you can right-click, select Properties from the context menu, and then select the Details tab of the resulting dialog. On a Mac, you can right-click and select Get Info.
Remember, the reason why we made the image width and height into global variables is so that our script becomes more generic and reusable. You probably want to create your own graphics for the widget, for example. You may wish to have the widget be a different size also. What we’re trying to do, therefore, is to make these things easier to customize without enforcing any knowledge of the underlying code.
Positioning textures on the screen
The next two lines of code specify the distance from the left and top of the screen where the green health bar’s rectangular texture should begin.
var healthMarginLeft: int = 41;
var healthMarginTop: int = 38;
We will use the healthMarginLeft variable to specify the horizontal position (from the left of the screen) where the texture’s rectangle will begin. And Similarly, we’ll use the healthMarginTop to specify the vertical position from the top of the screen.
In computer graphics, we often refer to the horizontal axis as x and the vertical axis as y. The top-most and left-most position on the screen is expressed as position (0,0) in (x,y).
So, if our variables define x and y coordinates, it could be expressed like this: (healthMarginLeft,healthMarginTop). The point (41,38) is where the top-most and left-most corner of our texture will be placed. So, it will have a left margin (a distance to the far-left hand side of the screen) of 41 pixels. And it will have a top-margin of 38 pixels.
But keep in mind that we haven’t drawn anything yet. We are only setting up the variables that we will use to size and position our textures a little later.
The next four lines are very similar to the last four we’ve looked at. They do the same thing, but they define a width, height, and margins for the background texture and top frame texture.
var frameWidth : int = 266;
var frameHeight: int = 65;
var frameMarginLeft : int = 10;
var frameMarginTop: int = 10;
To keep things as simple as possible, we cropped the background layer and the top-most layer images exactly the same size. That way, we can use the same dimensions and margins to position and render those two layers. The green health bar was an exception, which we cropped uniquely because it has to shrink or stretch to represent losing or gaining health.
The OnGui() function
The next stanza in the code is where the GUI functionality is really pulled together. When a script contains the OnGUI() function and is enabled in the game, it gets called every frame just like the Update() function.
function OnGUI () {
...
}
It is within this function that we create the GUI controls that will be seen on the screen. Which brings us to the first line of code within the function – the creation of a GUI texture. Think of a GUI texture as a 2D image that is parallel to the screen facing the user.
GUI.DrawTexture( Rect(frameMarginLeft,frameMarginTop, frameMarginLeft + frameWidth, frameMarginTop + frameHeight), backgroundTexture, ScaleMode.ScaleToFit, true, 0 );
This method can be understood by looking at the documentation in the Unity API. Take a moment now to review the API documentation for the GUI.DrawTexture function in the Unity API documentation.
API is an acronym for Application Programmer’s Interface; it’s the official documentation for a software development system. Each development language or system usually has a standard set of documents that comprise the API. Learning how to read and understand the API documentation for your platform is critical to your success as a developer!
For now, we’re going to try to explain very briefly how to understand this function’s documentation. Let’s break it down…
GUI.DrawTexture
static function DrawTexture (position, image, scaleMode, alphaBlend, imageAspect) : void
Everything within the parenthesis represent values that you pass into the function when you call it. Below, we provide the type of each value and a description.
- position is of the type Rect. In API documentation, that’s usually stated as position : Rect (the variable, a colon, and then the type; it means ‘this function expects a position to be passed in when you call it and the position needs to be a Rect’). The position : Rect, in this case, is the rectangle within the screen to draw the texture in.
- image is of the type Texture (image : Texture), which is the texture to display within the given Rect.
- scaleMode is one of either of the following constants: ScaleMode.StretchToFill, ScaleMode.ScaleAndCrop, or ScaleMode.ScaleToFit. This defines how to scale the image when the aspect ratio of it doesn’t fit the aspect ratio to be drawn within. That is to say – when the proportional relationship between the image’s width and height does not match that of the rectangle that you’re drawing it within.
- alphaBlend is of type boolean, which means either true or false. If the image has alpha blend information, then you can set this to true so that the image will blend with the background using the alpha information in the image. If the image has no alpha information, then we could set this to false.
- imageAspect is of type float, which means a floating point number (basically, a decimal number). If you pass in 0 (the default), the aspect ratio from the image is used. Pass in w/h for the desired aspect ratio. This allows the aspect ratio of the source image to be adjusted without changing the pixel width and height.
Drawing the first layer of the health status indicator
So, now that we kind of understand how the GUI.DrawTexture() function works, let’s take another look at the line we use to draw the first layer (the red that shows through when the green health bar is shrunk).
GUI.DrawTexture( Rect(frameMarginLeft,frameMarginTop, frameMarginLeft + frameWidth, frameMarginTop + frameHeight), backgroundTexture, ScaleMode.ScaleToFit, true, 0 );
Let’s break it down a little. Starting with the function call itself:
GUI.DrawTexture(
GUI is an object that Unity engine makes available to us. We know this because it’s documented in the API. The object has several functions that you could call from it using the dot notation. If you type the word GUI in the Unity editor and then a period character, the code completer should show a pop-up menu of the functions that are available on that object.
TIP: You can jump quickly to the API from within the MonoDevelop editor by selecting the menu Help > Unity API Reference.
The next thing that is passed into the function is the Rect which defines the position and size of the rectangle on the screen.
Rect(frameMarginLeft,frameMarginTop, frameMarginLeft + frameWidth, frameMarginTop + frameHeight)
To construct a Rect object we can use Rect(left, top, width, height). Instead of hard-coding values, we put variables in those spots. But notice that when we define the width and height, we add the margin. Since the rectangle will start after the margin, then we must increase the size of the rectangle by the amount of the margin.
Anyway, the statement then continues with…
, backgroundTexture, ScaleMode.ScaleToFit, true, 0 );
The backgroundTexture is a reference to the image that you drag and drop into the variable’s slot in the Inspector when using the script. The meaning of the rest of the values has already been described above.
The last two GUI.DrawTexture lines are for the middle layer and the foreground layer. The bottom layer and top layer are defined the same except for their texture. So, let’s just focus now on that middle layer – the green health status bar.
There’s something special about it…
Drawing the middle layer (the green bar)
GUI.DrawTexture( Rect(healthMarginLeft,healthMarginTop,healthWidth + healthMarginLeft, healthHeight), foregroundTexture, ScaleMode.ScaleAndCrop, true, 0 );
Notice that the ScaleMode used in the DrawTexture function is different than what’s defined for the bottom and top layers. That’s because the size of the green bar in the middle layer is going to change size as the game is played. If the ScaleMode is not set just so, the texture will do some strange things when sized.
Conclusion
You’ve finished the second in a multi-part series. This post was focused on teaching you how our script works as well as a little bit about programming.
In Part 3, we’ll learn how to reduce or increase health through code while the game is running.