Display character health on the UI
Tutorial
·
Beginner
·
+10XP
·
25 mins
·
(906)
Unity Technologies

In this tutorial, you’ll create a script to adjust the health display bar based on the player character’s health.
By the end of the tutorial, you’ll be able to do the following:
- Resize a UI element based on a variable.
- Make your script accessible without a reference using a static class member.
1. Overview
Now you’ve made your health display, you can use it to display information for the player. In this tutorial you’ll create a new script to adjust the health display bar based on the player character’s health.
By the end of the tutorial, you’ll be able to do the following:
- Resize a UI element based using a variable.
- Make your script accessible without a reference using a static class member.
Working on your own project?
This tutorial is part of Beginner 2D: Adventure Game, but you might find it useful if you want to learn about creating UI to display information using UI Toolkit. For further guidance on creating UI with this system, refer to the UI Toolkit documentation.
Note: For the purposes of this tutorial, we chose to use the Ruby’s Adventure asset set, and the file paths used in instructions will reflect this. If you chose another asset set, the file names will be the same, but in your corresponding theme folder.
2. Create a script to update the health display
Now that you’ve created your UI, it’s time to make the size of the health bar change based on the player character’s health.
Follow these instructions to create a script that updates the health display:
1. In the Project window, navigate to your Scripts folder, create a new C# script, and name it “UIHandler”.
You need to assign the script to the UIDocument GameObject.
2. Select the UIDocument GameObject in the Hierarchy window and drag and release the script over the Add Component button or use the search functionality.

3. Double-click the UIHandler script to open it in your IDE.
3. Implement basic health display functionality
To set up the UI Handler script so that a variable controls the health percentage displayed, follow these instructions:
1. Go to the namespaces at the top of the script and add the following instruction to the bottom of the list:
using UnityEngine.UIElements;This namespace provides access to the UI Toolkit system.
2. Go to the top of the UIHandler class, above the Start function. Declare a public float variable called CurrentHealth and set it to 0.5:
public float CurrentHealth = 0.5f;Note: This is a temporary variable for testing the script before you write a function that uses the correct health value in the PlayerController script.
3. In the Start function, add an instruction to get the UI Document component that’s on the same UI Document GameObject as this script:
UIDocument uiDocument = GetComponent<UIDocument>();4. Add a second instruction to get the root of the UI Hierarchy (currently the HealthBarBackground VisualElement) and call a function named “Q” on it:
VisualElement healthBar = uiDocument.rootVisualElement.Q<VisualElement>("HealthBar");Here’s an explanation of this code:
- The root of the UI Hierarchy is accessed through rootVisualElement.
- As you’ve encountered previously, the VisualElement is accessed using the dot operator.
- The Q function is short for Query. You can use this function to find a particular VisualElement in the Hierarchy window that matches multiple search parameters — in this case you are using the element name.
- Query is a generic function, because you can use it to query lots of different types. The specific type you are looking for is provided in angle brackets — in this case, it’s a VisualElement named “HealthBar”.
Note: GetComponent, which you’ve used previously, is also a generic function: you can use it to get any kind of component, but you need to specify which one you need when you use the function.
5. Add a final instruction to resize the health bar based on CurrentHealth variable value:
healthBar.style.width = Length.Percent(CurrentHealth * 100.0f);Before you read on, take a moment to review this code — what do you already understand?
- style contains all the VisualElement properties that you can set with UI Builder, and it is the width that you are resizing with this instruction.
- The special function Length defines a size in Percent (one of the various size types available for the property).
Note: If you provide a value to the Width property directly, the UI Toolkit system will interpret it as a pixel size rather than a percentage.
6. Delete the Update function as we won't be using it, and save your script changes.
7. Return to the Unity Editor and enter Play mode to test your changes. You set your temporary health variable to 0.5f, so the bar should fill 50% of the frame (0.5 * 100 = 50) .

8. Exit Play mode when you have finished testing.
4. Challenge: Write a function to change character health
Now that you’ve checked the basic functionality of your UIHandler script, you need to write a function to display the player character’s current health level.
Use the following guidance to help you as you write your new function:
- At the top of the UIHandler class, remove the temporary CurrentHealth variable. Declare a new private variable of the VisualElement type called “m_Healthbar”. This variable is a member of the UIHandler class — you’ll use it to store the reference to HealthBar and access that reference within the script outside of the Start function.
- In the Start function, update the instruction that gets the HealthBar reference to set the new m_Healthbar variable.
- Below the Start function, add a new public function called “SetHealthValue” for the instruction that sets the HealthBar element’s width. This function needs to take a float value between 0 and 1 as a parameter.
- Back in the Start function, add an instruction that passes a parameter of 1.0f to SetHealthValue.
Check your updated script
When you’ve revised the script yourself, review the completed example below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class UIHandler : MonoBehaviour
{
private VisualElement m_Healthbar;
// Start is called before the first frame update
void Start()
{
UIDocument uiDocument = GetComponent<UIDocument>();
m_Healthbar = uiDocument.rootVisualElement.Q<VisualElement>("HealthBar");
SetHealthValue(1.0f);
}
public void SetHealthValue(float percentage)
{
m_Healthbar.style.width = Length.Percent(100 * percentage);
}
}5. What are static class members?
So far in this course, you’ve declared variables in scripts to contain references. For example, at the moment your HealthCollectible script passes 1 as a parameter to the ChangeHealth function in the PlayerController script. However, you can create a public variable to contain the health increase value and reference that variable as the parameter instead. There would be a copy of that public variable for every instance of the health collectible object, because each collectible can provide a different health increase for the player.
However, if you make a variable or a function a static member of its class, it will always access the same space in the computer’s memory rather than each instance of the member having an individual space. If you made a static variable to control the collectible’s health increase, then changing it on one collectible instance would change the increase for every collectible in your game.
Static members can be very useful when there’s only one version of something in your game. You can also access static members using their class name rather than a reference.
You’ve actually already used class names to access static members in this course.
Time.deltaTime
deltaTime is a static variable member of the Time class. You can access this variable by using its name, rather than the following two instructions you’d need if it wasn’t a static member:
Time myTime = gameObject.GetComponent<Time>();
myTime.deltaTime;Debug.Log
Log is a static function within the Debug class. You don’t need to get a reference to Debug to print a message to the Console window, you can just call the Log function directly using its class name.
6. Revise your script to make it accessible without a reference
To revise your UIHandler script so that it can be accessed without a reference, follow these instructions:
1. At the top of the UIHandler class, declare a new static property:
public static UIHandler instance { get; private set; }Some of this declaration should be familiar to you, but some information is new:
- The get property is public and static. You can write UIHandler.instance in any script within your Unity project and it will call the get property.
- The set property is private; you should only be able to change the instance property within the UIHandler script.
2. Above the Start function, add a new Awake function:
private void Awake()
{
}This function is called as soon as the object the script is attached to is created. UIHandler is attached to the UIDocument GameObject, which will be created when the game launches.
3. In the Awake function, add an instruction to store the keyword “this” in your static property:
instance = this;The instruction stores the special C# keyword this in the instance property; the keyword is a reference to the current instance of the UIHandler class executing the Awake function.
4. Save your changes.
How have you changed the script functionality?
Now, when the game starts, Unity calls the Awake function and the UIHandler script stores itself in the static member property instance. You can call UIHandler.instance in any other script, which will return the UIHandler class to the script making the call.
This approach works for the UIHandler script because there is only one GameObject with the UIHandler script attached (the UIDocument GameObject). Static members of a class are shared across all instances of that script; if you changed the health increase value for one collectible GameObject, that change would be applied to every collectible in the game.
The UIHandler class follows the singleton pattern, a specific design approach for situations when you will only need one instance of a class. This pattern is perfect for situations like the UIHandler, which is a single class to control everything related to the UI heads-up display in your game.
7. Update the PlayerController script
You’ve prepared everything in the UIHandler script, now you need to adjust the PlayerController script so that the health values that it contains are reflected on the UI display.
To update the script, follow these instructions:
1. Open the PlayerController script in your IDE.
2. Locate the ChangeHealth function, at the bottom of your script.
3. Delete the instruction that displays the health values in the Console window by calling Debug.Log — you won’t need this any more.
4. Add the following instruction in the place of the one you deleted:
UIHandler.instance.SetHealthValue(currentHealth / (float)maxHealth);Before you read on, take a moment to review this code — what do you already understand?
- This instruction passes the ratio of the currentHealth value over the maxHealth value to the SetHealthValue function in the UIHandler script.
- Both currentHealth and maxHealth are integers, and in C# the division of two integers can only return another integer (whole number). For example, 2 / 4 would give 0, because the fractional remainder gets ignored. A whole number value will not work for your percentage calculation in SetHealthValue.
- The (float) keyword that precedes maxValue is C# syntax that makes the compiler treat maxHealth as a floating point value, which resolves the division problem. When at least one value in the division calculation is a float (floating point), the calculation returns a float value. In the example, 2 / 4.0 would give 0.5.
5. Save your changes.
6. Return to the Unity Editor and test your game. The health bar on the heads-up display will now increase and decrease as the player character’s health changes!
8. More things to try
If you want to further develop your skills, explore new concepts, or improve your project, check out some of the optional activities below. Each one is tagged as either Easy, Medium, or Difficult, so you can choose the level of challenge.
These activities are entirely optional, so if you’re not interested, no problem — just skip this step. We do recommend attempting at least one of them in order to get the most out of this learning experience. Good luck!
Easy: Adjust the positioning for optimal player experience
Now that the HUD is fully-functioning, take some time to test its positioning. If you can ask someone else to test your game-in-progress and get their feedback, even better! You might find that you want to move the display to the upper-right corner instead, or that the general position is good but tweaking it a little more makes a positive difference.
9. Check your scripts
If you’re completing the 2D Beginner course, take a moment to check that your scripts are correct before continuing.
PlayerController.cs
Important: If you completed any extension work in your script beyond exposing the variable that controls movement speed, this will not be reflected in the reference script below.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
// Variables related to player character movement
public InputAction MoveAction;
Rigidbody2D rigidbody2d;
Vector2 move;
public float speed = 3.0f;
// Variables related to the health system
public int maxHealth = 5;
int currentHealth;
public int health { get { return currentHealth; }}
// Variables related to temporary invincibility
public float timeInvincible = 2.0f;
bool isInvincible;
float damageCooldown;
// Start is called before the first frame update
void Start()
{
MoveAction.Enable();
rigidbody2d = GetComponent<Rigidbody2D>();
currentHealth = maxHealth;
}
// Update is called once per frame
void Update()
{
move = MoveAction.ReadValue<Vector2>();
if (isInvincible)
{
damageCooldown -= Time.deltaTime;
if (damageCooldown < 0)
isInvincible = false;
}
}
// FixedUpdate has the same call rate as the physics system
void FixedUpdate()
{
Vector2 position = (Vector2)rigidbody2d.position + move * speed * Time.deltaTime;
rigidbody2d.MovePosition(position);
}
public void ChangeHealth (int amount)
{
if (amount < 0)
{
if (isInvincible)
return;
isInvincible = true;
damageCooldown = timeInvincible;
}
currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
UIHandler.instance.SetHealthValue(currentHealth / (float)maxHealth);
}
}
UIHandler.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class UIHandler : MonoBehaviour
{
private VisualElement m_Healthbar;
public static UIHandler instance { get; private set; }
// Awake is called when the script instance is being loaded (in this situation, when the game scene loads)
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
UIDocument uiDocument = GetComponent<UIDocument>();
m_Healthbar = uiDocument.rootVisualElement.Q<VisualElement>("HealthBar");
SetHealthValue(1.0f);
}
public void SetHealthValue(float percentage)
{
m_Healthbar.style.width = Length.Percent(100 * percentage);
}
}
10. Next steps
In this unit, you created a display to help the player track their character’s health, which will be very helpful because you’re going to add more challenges for them to encounter. So far there are only static hazards in your game, but in the next unit you’ll add enemies and a way for the player character to stop those attacks.