Player Health

In this tutorial, you will manage and display a player's health. The method used should be useful in any game in which the player takes damage from an enemy.

Player State

Create a class to store the state of the player.



Open the project in which you want to implement player health. For demonstration purposes, the Platformer project will be used in this lesson.

Create a script named PlayerState in the Scripts folder and attach it to the player character. This script will be used to keep track of the player's state.

Open the script, remove the comments and the Update method, and add maxHealth and health as public properties of type float. Set the maxHealth to the maximum amount of health the player should be able to achieve. In the Start method set the initial value of health to that maximum value.

While both properties are public (to be accessible by other classes), you only want the maxHealth to be editable in the inspector. The health will be initialized in the Start method so any value entered in the inspector would be overwritten. The [HideInInspector] flag provides this ability.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerState : MonoBehaviour
{
    public float maxHealth = 100;

    [HideInInspector] 
    public float health;

    void Start()
    {
        health = maxHealth;
    }
}
            

You will expand on this later. But for now, the player has health!

Healthbar UI

Create a health bar to show the player's health.



Previously you learned how to display the player's health as text. Sometimes it is nice to have a visual that shows the amount of health you have relative to the maximum amount you could have. A health bar is a visual element to show this percentage of health.

Right-click in the Hiearchy and select UI > Panel. This should add a Panel to your Hierarchy along with a parent Canvas. UI (User Interface) elements should all be within a Canvas.

Zoom out so that you can see the entire Canvas. It is white, but mostly transparent. When you play the game, you should see this Canvas overlaying the camera view.

Think of the Canvas as a pair of glasses for the camera. The canvas is always visible regardless of where the camera moves or what direction it faces.



With the Panel selected, locate the Image component in the Inspector Window. Click on the Color field and use the slider to change the alpha value to zero. This will make the panel transparent.



Rename the panel to "Healthbar", so that you can easily keep track of it.

Then grab the corners of the panel and resize it to create a small bar at the top left. This will be the space where the health bar will be shown.



In the Hierarchy, right-click on Healthbar and select UI > Image. Rename the Image to Health. Change the Color property of Image component for the Health to a color that you feel represents health.

The image has a width of 100 and height of 100, so it will need to be rescaled to match its parent object. After clicking the anchor thumbnail, hold Alt and select the bottom right icon to stretch the image to match its parent's width and height.



Notice the scale of Rect Transform component of Health is 1, 1, 1. This is why the Health has the same size at its parent element, Healthbar, taking up 100% of its size. Change the X value to 0.5 so it has 50% width of the Healthbar.

So the Health will represent the percentage of health the player has remaining while the Healthbar represents the maximum amount of health the player may have. Unfortunately, the Health is centered on the Healthbar.

To anchor the Health to the left side of the Healthbar, click on the anchor thumbnail located in the Rect Transform component of the Health. A grid of Anchor presets will appear.

Hold the Shift (to also set pivot) and Alt (to also set position) keys down and click on the preset for left and middle to set the anchor. Then, click on the X label of the Scale and drag to see how the Health will appear for x values between 0 and 1.



The UI element for the healthbar is all set up. Now you just need to add code that will update the healthbar anytime the player's health changes.

Dealing Damage

Add an enemy that can damage the player.



If you have completed the Enemy Inheritance lesson, open the EnemyBehavior script that you had created. Otherwise, create an EnemyBehavior script with the following code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyBehavior : MonoBehaviour
{

    protected GameObject target;
    private bool dead = false;
    private float timeSinceDeath = 0;
    public float delayUponDeath = 0.8f;

    protected virtual void Start()
    {
        target = GameObject.FindWithTag("Player");
    }

    protected virtual void Update()
    {
        if (dead)
        {
            timeSinceDeath += Time.deltaTime;
            if (timeSinceDeath > delayUponDeath)
            {
                Time.timeScale = 0.0f;
                Destroy(this.gameObject);
            }
        }
    }

    protected virtual void OnTriggerEnter(Collider other)
    {
        if (target != null && other.gameObject == target)
        {
            target.SetActive(false);
            dead = true;
        }
    }
}
            

Briefly, this script will be attached to a simple enemy. When a collision occurs between the player and enemy, the script causes the player to be immediately destroyed and then after a specified delay causes the enemy to be destroyed and the game to end. For a complete discussion of this code, including why the methods are protected and virtual, see the Enemy Inheritance lesson.

If you have completed the Enemy Inheritance lesson, add one of the enemy prefabs to the scene. Otherwise, create a Sphere named Enemy, position the Enemy near the player with a z value of 0, check the Is Trigger property of the Sphere Collider, and attach the EnemyBehavior script to the Enemy. Lastly, tag the player as Player.



Play the game to test that the player dies when they collide with the enemy.



Currently, a collision with the player (the target of the enemy) causes the player to immediately die.
protected virtual void OnTriggerEnter(Collider other)
{
    if (target != null && other.gameObject == target)
    {
        target.SetActive(false);
        dead = true;
    }
}
            

Modify this code to reduce the health of the player and only kill the player when the health has fallen to zero.
protected virtual void OnTriggerEnter(Collider other)
{
    if (target != null && other.gameObject == target)
    {
        PlayerState ps = target.GetComponent<PlayerState>();
        
        ps.health -= 12;
        if(ps.health < 0) ps.health = 0;

        if(ps.health == 0)
        {
            target.SetActive(false);
            dead = true;
        }
        print(ps.health);
    }
}
            

The ps variable stores a PlayerState and is initialized to be the PlayerState that is a component of the target. Through this PlayerState, you have access to all the public properties and methods of the PlayerState class.

The next portion of the code reduces the player's health by the amount of damage the enemy is dealing (12 damage in this case). If the health falls lower than zero it is set back to zero. You do not want the health to be negative since this value will be used for updating the Healthbar.

After the health has been reduced, a check is made to determine if the player should die. Because the health is hidden from in the inspector, the print statement is included to test if the health is being reduced as intended.

Save the code, return to Unity, and play the game with the console showing. You should see the health value printed out when the player runs into the enemy.



Notice that the player's health is reduced only at the moment the player enters the trigger of the enemy. That makes sense given the code is placed within the OnTriggerEnter method. If you run back and forth to collide into the enemy you will see the health continue to go down until finally the player dies.

Of course, different enemies may deal different amounts of damage. As the EnemyBehavior class is intended to be used for multiple types of enemies, it would be helpful to have a way to specify the amount of damage an enemy deals.

Add a damage property of type float to the EnemyBehavior class with an initial value of 1f.
protected GameObject target;
private bool dead = false;
private float timeSinceDeath = 0;
public float delayUponDeath = 0.8f;
public float damage = 1;
            

In the OnTriggerEnter method, use the value of the damage property instead of the constant 12 to reduce the health.
ps.health -= damage;
if (ps.health < 0) ps.health = 0;
            

Save the changes and return to Unity. With the Enemy selected, change the value of the Damage in the inspector to any value. Then play the game again with the console visible to ensure the appropriate amount of damage is dealt to the player upon collision with the enemy.

Updating the UI

Update the Healthbar every time the player's health changes.



Where should the code for updating the Healthbar be added? Is this a behavior of the enemy, of the player, or of the healthbar itself? This is up to interpretation and code could actually be added to any of these objects to achieve the desired behavior.

The PlayerState seems like a logical place if you conider the Healthbar as being connected to the player.

In the PlayerState class, update the code to:
public class PlayerState : MonoBehaviour
{
    public float maxHealth = 100;

    [HideInInspector]
    public float health;

    private RectTransform uiHealth;

    void Start()
    {
        health = maxHealth;
        uiHealth = GameObject.Find("Health").GetComponent<RectTransform>();
        updateHealthbar();
    }

    public void updateHealthbar()
    {
        float x = health / maxHealth;
        float y = uiHealth.localScale.y;
        float z = uiHealth.localScale.z;
        uiHealth.localScale = new Vector3(x, y, z);
    }
}
            

The uiHealth property is used to reference the RectTransform component of the Health GameObject that you set up within the Healthbar to show the player's current health. The GameObject.Find and GetComponent methods are used within the Start method to initialize the value of uiHealth.

The UpdateHealthbar method updates the Healthbar by adjusting the X value of the RectTransform's scale property. The method is called from the Start method so the Healthbar is updated to the initial health value when the game begins. The method is made public because it will also be called by other classes (such as the EnemyBehavior class).

The value of x is determined by dividing the current health of the player by their maxHealth. Consider examples given a maxHealth of 100.
• If health is 0, x will be 0 (0 divided by 100)
• If health is 100, x will be 1 (100 / 100)
• If health is 50, x will be 0.5 (50/100)

If you save the code and play the game now, you should see the Healthbar starts out full regardless of its initial size in the editor. However, it does not update when the enemy deals damage to the player.



In the EnemyBehavior class, add a call to the PlayerState's updateHealthbar method just after the health is reduced.
ps.health -= damage;
if (ps.health < 0) ps.health = 0;
ps.updateHealthbar();
            

Save and play the game again. You should see your health decrease every time you enter the player collides with the enemy.

Encapsulation

Understanding when to use public and private.



A core concept of Object Oriented Programming is encapsulation. This simply means bundling together the properties and methods that belong logically together within a class. You have been doing this with every script you have written.

Consider the PlayerState.cs script. It has properties (maxHealth, health, and uiHealth) and methods (Start and updateHealthbar) which are all bundled together in the PlayerState class.

Normally, properties from one class should not be visible to other classes. This OOP approach is called data hiding.

So far, you have made most of your properties public in order set in the editor. This approach does not hide the properties from other classes. By making the health property public you were able to access (see) and modify the value of the property from the EnemyBehavior class. There is another approach that is more consistent with OOP practice of data hiding.

In the PlayerState class, change the access modifier for the maxHealth and health properties to private. As health is now private and will not appear in the editor by default, the [HideInInspector] flag may be removed. Also, now that maxHealth is private, it will cease being visible in the editor even though you want it to appear. Add the [SerializeField] flag before the declaration of maxHealth to make it appear in the Inspector.
[SerializeField]
private float maxHealth = 100;

private float health;
private RectTransform uiHealth;
            

Now that all properties of PlayerState are private, they are hidden from other classes. This is data-hiding!

In Unity, public properties appear in the inspector by default. Use the [HideInInspector] flag in front of the declarartion of a public property to hide it in the inspector.

In Unity, private properties do not appear in the inspector by default. Use the [SerializedField] flag in front of a the declaration of a private property to show it in the Inspector.

If you save and return to Unity, you will see in the Console several errors in EnemyBehavior.cs of 'PlayerState.health' is inaccessible due to its protection level.

The error makes sense. You are attempting to access a private property health of the PlayerState class from the EnemyBehavior class. So how do you access private properties of a class from a different class?

Public methods can be used if/when access to private data is needed. For example, it is common to have a public getter method to get the value of a private property or to have a public setter method to set the value of a private property.

In the PlayerState class, add a getter and setter method for the health property so that other classes may have access to the property.
public float getHealth()
{
    return health;
}

public void setHealth(float h)
{
    health = h;
}
            

Because health is of type float, the getHealth method should return a float and the setHealth method should use a parameter of type float to set the value of health. Both methods are public so that they may be called from other classes.

Other public methods may also be useful. Add methods to increase or decrease the health by a specified value.
public void increaseHealth(float h)
{
    health += h;
}

public void reduceHealth(float h)
{
    health -= h;
}
            

Hopefully, you are realizing that this is much more work than just using a public property. So why use public methods when you could just make the property public in the first place? This is often helpful when changes to the property need to be validated to ensure the value of health is appropriate (between 0 and maxHealth) or when some other code should execute every time the value of health changes (such as updating the healthbar).

Modify the setHealth method to validate the value and update the healthbar after the value of health is changed.
public void setHealth(float h)
{
    if (h < 0) health = 0;
    else if (h > maxHealth) health = maxHealth;
    else health = h;
    updateHealthbar();
}
            

You have to decide how to handle bad values. If an attempt is made to set the health to -5, you could ignore the attempt and leave health at its current value? In this case, if there is an attempt to set the health to a value lower than 0, the value of health is set to 0. Similarly, if an attempt is made to set the health to a value higher than maxHealth, the value of health is set to maxHealth.

You could do similar validation in the other methods where the value of health changes. However, this would create redundant code that would be more difficult to maintain and lead to more coding errors.

Instead, modify the methods which change the value of health to do so by calling the setHealth method.
public void increaseHealth(float h)
{
    setHealth(health + h);
}

public void reduceHealth(float h)
{
    setHealth(health - h);
}
            

Back in the EnemyBehavior class, modify the OnTriggerEnter method to change the health of the player using the public properties.
if (target != null && other.gameObject == target)
{
    PlayerState ps = target.GetComponent<PlayerState>();

    ps.reduceHealth(damage);

    if (ps.getHealth() == 0)
    {
        target.SetActive(false);
        dead = true;
    }
    print( ps.getHealth() );
}
            

Play the game again to ensure the player's health decreases in the healthbar and in the console as expected.



If successful, remove the print statement at the end of the OnTriggerEnter method that was being used to print the value of health to te console.

This entire effort to hide class data did not change the behavior. Was it worth the time? Well, now the health will never be set to an invalid value and the healthbar will always update when the health property changes. Following this method of programming can reduce programming errors and save an enourmous amount of time, especially on large projects that require multiple programmers.

Damage Per Second

Giving a damgage per second (dps) option.



Currently, the enemy causes damage only at the moment the player enters the trigger of the enemy. This may be useful for items flying across the screen, such as the Fireball created in Bouncy Box or the enemies created in the Enemy Inheritance lesson.

However, for a stationary enemy, you may want to have them give damage continuously while the player is within the enemies trigger collider.

Before setting this up, move the code that deals damage to the player out of the OnTriggerEnter method.
protected virtual void OnTriggerEnter(Collider other)
{
    if (target != null && other.gameObject == target)
    {
        dealDamage();
    }
}

private void dealDamage()
{
    PlayerState ps = target.GetComponent<PlayerState>();

    ps.reduceHealth(damage);

    if (ps.getHealth() == 0)
    {
        target.SetActive(false);
        dead = true;
    }
}
            

By moving the code for dealing damage into its own seperate method, the code may be called upon from various places. First, make sure a call to the function is still in the OnTriggerEnter method.

You could set up your own timer similarly to the way the delay upon death was handled. However, Unity provides an OnTriggerStay method that may make this easier.

Add the OnTriggerStay method directly under the OnTriggerEnter method and include the same code within the method.
protected virtual void OnTriggerStay(Collider other)
{
    if (target != null && other.gameObject == target)
    {
        dealDamage();
    }
}
			

The OnTriggerStay method is called every frame in which a collision with the trigger is occurring. Save the code, return to Unity, set the damage to a low number such as 1, and play the game. Now the health of the player should continuously decrease while in contact with the enemy.



It would be nice to use the same script for enemies that cause damage only when the trigger is entered as well as those enemies that cause continuous damage.

Add a public bool property named continuousDamage with a default value of false.
protected GameObject target;
private bool dead = false;
private float timeSinceDeath = 0;

public bool continuousDamage = false;
public float delayUponDeath = 0.8f;
public float damage = 1.0f;
			

Add a conditional statement within the OnTriggerStay method so that damage is dealt only if the continuousDamage property is set to true.
protected virtual void OnTriggerStay(Collider other)
{
    if (target != null && other.gameObject == target)
    {
        if(continuousDamage) dealDamage();
    }
}
			

Save and play the game again. With the Continuous Damage property checked off, the player's health should only be reduced at the moment the player enters the enemy's trigger. With the Continuous Damage property checked on, the player's health should reduce every frame while the player is within the enemy's trigger.



Now you have the ability to cause damage on every frame. However, you do not have control over the frame rate so you the amount of damage per second will vary. Instead of reducing the health by the damage every frame, the health should be reduced by damage every second.

Update the dealDamage method to take the framerate into account when calling the PlayerState's reduceHealth property. As usual, this is done by multiplying the damage by the amount of time that has passes since the last time the dealDamage method was called.
private void dealDamage()
{
    PlayerState ps = target.GetComponent<PlayerState>();

    ps.reduceHealth(damage * Time.deltaTime);

    if (ps.getHealth() == 0)
    {
        target.SetActive(false);
        dead = true;
    }
}
			

Save the code and return to Unity. Set the Damage property to 12 and check on Continuous Damage. Play the game and you should see that the health is reduced by 12 every second.



Updating the UI

Update the Healthbar every time the player's health changes.



You can add a little more to the Healthbar UI to provide more indication to the player about how much health they have left.

First, add some text above the health bar to let the player know that this represents their health.



Then, duplicate the health bar, change the name to HealthBack, change its scale to 1, 1, 1, change the color to black, and move it up in the hierarchy so that it appears at the back. That way, the player knows how much health is full player health.



Lastly, add a backing to contain both the text and the images. That way, the player knows the text is connected to that health bar. This technique is helpful when you have multiple different bars for the player showing different variables they need to track.

You can do this by duplicated the HealthBack object, and turning it to a light shade of gray with transparency.