Almost every mobile game needs a health system. Whether your player loses hearts in a platformer, takes damage from enemies in an action game, or burns through lives in a puzzle game, the underlying mechanic is the same: a value goes down, a UI updates, and when it hits zero, something happens. In this tutorial, you will build a complete health and lives system in Unity from scratch — with a working health bar, a lives counter, damage handling, a death event, and a simple respawn flow — all optimized for mobile performance.
What You Will Build
By the end of this tutorial, your Unity project will have:
- A PlayerHealth script that tracks current HP and max HP
- A LivesManager script that tracks remaining lives and triggers game over
- A health bar UI that fills and empties smoothly using Unity’s Image fill amount
- A lives counter displayed as a text label in the HUD
- A damage and death flow with a short invincibility window after taking a hit
- A basic respawn system that resets health and repositions the player
All scripts are written in C# and designed to be lightweight and easy to extend. This system plugs cleanly into any 2D mobile game, including the platformer and brick breaker projects covered in other guides on this site.
Project Setup
This tutorial assumes you have Unity 2022 LTS or later installed and a basic 2D mobile project open. You do not need any prior scripts — we are building this from zero.
You will need:
- A Player GameObject with a Rigidbody2D and a Collider2D already attached
- A Canvas set to Screen Space – Overlay for the HUD
- An Image component inside the Canvas for the health bar (set Fill Method to Horizontal)
- A TextMeshPro – Text component for the lives counter
If you need a refresher on setting up a Canvas and UI elements, check out our guide on Touch-Friendly Menus for Mobile — the same setup principles apply here.
Step 1: The PlayerHealth Script
Create a new C# script called PlayerHealth and attach it to your Player GameObject. This script handles all HP logic: taking damage, triggering death, and managing the brief invincibility window that prevents a player from being hit multiple times in a single frame.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class PlayerHealth : MonoBehaviour
{
[Header("Health Settings")]
public int maxHealth = 3;
public float invincibilityDuration = 1.5f;
[Header("UI Reference")]
public Image healthBarFill;
private int currentHealth;
private bool isInvincible = false;
public System.Action OnPlayerDeath;
void Start()
{
currentHealth = maxHealth;
UpdateHealthBar();
}
public void TakeDamage(int amount)
{
if (isInvincible) return;
currentHealth -= amount;
currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
UpdateHealthBar();
if (currentHealth <= 0)
{
OnPlayerDeath?.Invoke();
}
else
{
StartCoroutine(InvincibilityWindow());
}
}
public void ResetHealth()
{
currentHealth = maxHealth;
isInvincible = false;
UpdateHealthBar();
}
private void UpdateHealthBar()
{
if (healthBarFill != null)
healthBarFill.fillAmount = (float)currentHealth / maxHealth;
}
private IEnumerator InvincibilityWindow()
{
isInvincible = true;
yield return new WaitForSeconds(invincibilityDuration);
isInvincible = false;
}
}
A few things worth noting in this script. The OnPlayerDeath action is an event — rather than calling specific methods directly, we broadcast a signal that any other script can subscribe to. This keeps the health system decoupled from the rest of your game. The Mathf.Clamp call on line 27 ensures health never goes below zero or above maximum, which prevents edge cases with multi-damage collisions.
Step 2: The LivesManager Script
Create a second script called LivesManager. This script listens for the death event from PlayerHealth, subtracts a life, either respawns the player or triggers game over, and keeps the UI lives counter updated.
using UnityEngine;
using TMPro;
public class LivesManager : MonoBehaviour
{
[Header("Lives Settings")]
public int startingLives = 3;
[Header("References")]
public PlayerHealth playerHealth;
public TextMeshProUGUI livesText;
public Transform respawnPoint;
public GameObject gameOverPanel;
private int currentLives;
void Start()
{
currentLives = startingLives;
UpdateLivesUI();
if (playerHealth != null)
playerHealth.OnPlayerDeath += HandlePlayerDeath;
}
void OnDestroy()
{
if (playerHealth != null)
playerHealth.OnPlayerDeath -= HandlePlayerDeath;
}
private void HandlePlayerDeath()
{
currentLives--;
UpdateLivesUI();
if (currentLives <= 0)
{
TriggerGameOver();
}
else
{
RespawnPlayer();
}
}
private void RespawnPlayer()
{
if (respawnPoint != null)
playerHealth.transform.position = respawnPoint.position;
playerHealth.ResetHealth();
}
private void TriggerGameOver()
{
if (gameOverPanel != null)
gameOverPanel.SetActive(true);
Time.timeScale = 0f;
}
private void UpdateLivesUI()
{
if (livesText != null)
livesText.text = "Lives: " + currentLives;
}
}
Notice the OnDestroy unsubscription on line 24. This is important on mobile: if the LivesManager GameObject is destroyed before the PlayerHealth, the event delegate would point to a dead object and cause a null reference error. Always unsubscribe when you subscribe.
The Time.timeScale = 0f call on game over pauses all physics and animations instantly — the simplest and most reliable way to freeze the game on mobile without managing individual component states. When the player restarts, remember to reset it with Time.timeScale = 1f.
Step 3: Setting Up the Health Bar UI
In the Unity Editor:
- Inside your Canvas, create an empty GameObject named
HealthBar. - Add a child Image named
HealthBarBackground— set its color to a dark gray. This is the empty bar. - Add a second child Image named
HealthBarFill— set its color to green or red, then go to Image Type → Filled and set Fill Method → Horizontal. This is the fill that shrinks as HP drops. - In the PlayerHealth Inspector, drag
HealthBarFillinto the Health Bar Fill field.
The fill amount property on a UI Image ranges from 0 (empty) to 1 (full), which maps perfectly to a 0-to-maxHealth ratio. No extra math required — the script handles it automatically in the UpdateHealthBar() method.
Step 4: Connecting the Inspector References
With both scripts created, wire everything up in the Inspector:
- Create an empty GameObject named
LivesManagerin the scene and attach the LivesManager script to it. - Drag the Player GameObject into the Player Health field.
- Drag your TMP lives label into the Lives Text field.
- Create an empty GameObject named
RespawnPoint, position it at the level start, and drag it into the Respawn Point field. - Create a UI panel for game over (set to inactive by default) and drag it into the Game Over Panel field.
Step 5: Triggering Damage From Enemies or Hazards
The health system only works if something calls TakeDamage(). Here is a minimal hazard script you can attach to any enemy or spike object:
using UnityEngine;
public class DamageOnContact : MonoBehaviour
{
public int damageAmount = 1;
private void OnTriggerEnter2D(Collider2D other)
{
PlayerHealth health = other.GetComponent<PlayerHealth>();
if (health != null)
{
health.TakeDamage(damageAmount);
}
}
}
Attach this to any GameObject that should hurt the player, make its collider a Trigger, and set the damage amount in the Inspector. The invincibility window in PlayerHealth automatically handles repeated collisions, so you do not need extra logic here.
Step 6: Adding a Smooth Health Bar Animation (Optional)
The basic health bar snaps instantly to its new value. For a more polished feel, you can lerp the fill amount over a short duration. Replace the UpdateHealthBar() method in PlayerHealth with this coroutine version:
private IEnumerator AnimateHealthBar(float targetFill)
{
float startFill = healthBarFill.fillAmount;
float elapsed = 0f;
float duration = 0.3f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
healthBarFill.fillAmount = Mathf.Lerp(startFill, targetFill, elapsed / duration);
yield return null;
}
healthBarFill.fillAmount = targetFill;
}
private void UpdateHealthBar()
{
float target = (float)currentHealth / maxHealth;
StartCoroutine(AnimateHealthBar(target));
}
The 0.3-second duration is short enough that it feels responsive but long enough that the player registers the damage visually. On low-end Android devices, keep the duration at or below 0.4 seconds — longer animations can make the UI feel sluggish on devices with limited GPU memory.
Mobile Performance Considerations
Health systems are lightweight by nature, but there are a few mobile-specific habits worth building early:
- Cache component references in Start(). Never call
GetComponent()insideUpdate()or inside damage methods that fire frequently. All references in these scripts are cached at initialization. - Avoid FindObjectOfType() at runtime. Both scripts use Inspector references instead of runtime lookups — this is significantly faster on mobile processors.
- Use a single Canvas for all HUD elements. Each Canvas generates a draw call. Keeping your health bar, lives counter, and score on a single Canvas reduces GPU overhead.
- Reset TimeScale reliably on scene reload. If your game over screen leads to a scene reload, add
Time.timeScale = 1fin theAwake()orStart()of your first script on the new scene — otherwise the game may load frozen.
Extending the System
Once the core system is working, here are natural next steps depending on your game type:
- Health pickups: Add a
Heal(int amount)public method to PlayerHealth and call it from a collectible script — the same pattern asTakeDamage(), in reverse. - Shield mechanic: Add a boolean
hasShieldto PlayerHealth. If true, the first call toTakeDamage()sets it to false instead of reducing HP. - Heart icons instead of a bar: Replace the fill Image with a row of heart icons. In
UpdateHealthBar(), loop through an array of heart Images and toggle their enabled state based oncurrentHealth. - Persistent lives between scenes: Move the lives count to a static variable or a dedicated GameManager singleton so it survives scene transitions. Pair this with the save system covered in our Unity JSON Save System tutorial.
Frequently Asked Questions
Why is my health bar not updating visually?
The most common cause is that the Health Bar Fill field in the Inspector is empty. Make sure you have dragged the fill Image (not the background Image) into the field on the PlayerHealth component. Also confirm the Image's Image Type is set to Filled — a regular Image ignores the fill amount property.
The player is dying instantly from a single enemy. What is wrong?
This usually happens when the enemy collider is triggering multiple OnTriggerEnter2D calls per frame — common with fast-moving objects on mobile. The invincibility window in the script should prevent this, but if you have set invincibilityDuration to 0, there is no protection. Set it to at least 0.5 seconds to start.
How do I make the game over screen restart the level?
Add a button to your game over panel and connect it to a method that calls SceneManager.LoadScene(SceneManager.GetActiveScene().name) after resetting Time.timeScale = 1f. Make sure using UnityEngine.SceneManagement; is at the top of the script.
Can this system work with a top-down or endless runner game?
Yes. The PlayerHealth and LivesManager scripts are not tied to any specific game type. The only thing to adjust is the respawn logic in LivesManager — in an endless runner, instead of moving the player to a fixed position, you would reset the player's forward progress and restart the obstacle spawn sequence.
Final Thoughts
A health and lives system is one of those foundational mechanics that appears in almost every game genre — but it is rarely taught as its own isolated topic. Building it clean from the start, with proper event-based communication and mobile performance habits, saves a lot of refactoring later when your game grows.
The scripts in this tutorial are intentionally minimal. Every line is there for a reason, and every method is designed to be extended without rewriting the core. Once you have the system running in your project, you will find that adding shields, power-ups, checkpoints, and save integration all slot in naturally.
For more hands-on Unity mechanics, explore our Small Game Mechanics & Prototyping category, or jump into a complete mini project in the Unity Mini Projects section.

Game developer with over 10 years of professional experience specializing in the mobile sector. George’s journey began with a passion for indie development, leading him to contribute to several successful mobile titles, including the critically acclaimed puzzle-platformer ChronoShift and the top-down strategy game Pocket Empires.
