Need something simpler? See the (basic version).
Part of these game systems:
intermediate UI PRO

Health Bar UI Pro

Advanced health bar with delayed damage indicator, shield overlay, boss mode with phase markers, and segmented health display.

Unity 2022.3+ · 5.2 KB · HealthBarUIPro.cs

How to Use

1

Create UI Canvas with background Image + fill Image (set to Filled type)

2

Attach HealthBarUIPro to the bar root

3

Assign fill image and optional delayed damage image (behind fill)

4

Connect HealthSystem.OnHealthChanged to SetHealth()

5

For shields: add separate fill image, call SetShield()

6

For boss bars: enable Boss Mode, set name text and phase count

7

Call ShowBossBar() when boss encounter starts

8

Enable segments for segmented health display (like Hollow Knight)

9

Billboard option auto-faces camera for world-space bars

Source Code

HealthBarUIPro.cs
C#
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class HealthBarUIPro : MonoBehaviour
{
    [Header("Health Bar")]
    [SerializeField] private Image fillImage;
    [SerializeField] private Gradient healthGradient;
    [SerializeField] private float smoothSpeed = 5f;

    [Header("Delayed Damage")]
    [SerializeField] private Image delayedFillImage;
    [SerializeField] private Color delayedColor = new Color(1f, 1f, 1f, 0.7f);
    [SerializeField] private float delayedDelay = 0.6f;
    [SerializeField] private float delayedSpeed = 2f;

    [Header("Shield")]
    [SerializeField] private Image shieldFillImage;
    [SerializeField] private Color shieldColor = new Color(0.3f, 0.7f, 1f, 0.8f);
    [SerializeField] private GameObject shieldContainer;

    [Header("Boss Mode")]
    [SerializeField] private bool isBossBar;
    [SerializeField] private TextMeshProUGUI bossNameText;
    [SerializeField] private RectTransform phaseMarkerContainer;
    [SerializeField] private GameObject phaseMarkerPrefab;
    [SerializeField] private CanvasGroup bossGroup;
    [SerializeField] private float bossFadeSpeed = 3f;

    [Header("Damage Flash")]
    [SerializeField] private Image backgroundImage;
    [SerializeField] private Color flashColor = new Color(1f, 0.2f, 0.2f, 0.5f);
    [SerializeField] private float flashDuration = 0.15f;

    [Header("Segments")]
    [SerializeField] private bool showSegments;
    [SerializeField] private int segmentCount = 5;
    [SerializeField] private RectTransform segmentContainer;
    [SerializeField] private GameObject segmentLinePrefab;

    [Header("Portrait")]
    [SerializeField] private Image portraitImage;
    [SerializeField] private Sprite portraitSprite;

    [Header("Billboard")]
    [SerializeField] private bool billboardToCamera = true;

    private float targetFill = 1f;
    private float currentFill = 1f;
    private float delayedFill = 1f;
    private float delayedTimer;
    private float shieldFill;
    private float flashTimer;
    private Color bgOriginalColor;
    private bool bossVisible;
    private Camera mainCam;

    private void Awake()
    {
        if (backgroundImage != null)
            bgOriginalColor = backgroundImage.color;

        if (portraitImage != null && portraitSprite != null)
            portraitImage.sprite = portraitSprite;

        if (delayedFillImage != null)
            delayedFillImage.color = delayedColor;

        if (shieldFillImage != null)
            shieldFillImage.color = shieldColor;

        if (shieldContainer != null)
            shieldContainer.SetActive(false);

        if (bossGroup != null && isBossBar)
        {
            bossGroup.alpha = 0f;
            bossVisible = false;
        }

        mainCam = Camera.main;
        GenerateSegments();
    }

    private void Update()
    {
        // Smooth health fill
        currentFill = Mathf.Lerp(currentFill, targetFill, smoothSpeed * Time.deltaTime);
        if (fillImage != null)
        {
            fillImage.fillAmount = currentFill;
            fillImage.color = healthGradient.Evaluate(currentFill);
        }

        // Delayed damage bar
        if (delayedFillImage != null)
        {
            if (delayedFill > targetFill)
            {
                delayedTimer -= Time.deltaTime;
                if (delayedTimer <= 0f)
                    delayedFill = Mathf.Lerp(delayedFill, targetFill, delayedSpeed * Time.deltaTime);
            }
            else
            {
                delayedFill = targetFill;
            }
            delayedFillImage.fillAmount = delayedFill;
        }

        // Damage flash
        if (flashTimer > 0f)
        {
            flashTimer -= Time.deltaTime;
            if (flashTimer <= 0f && backgroundImage != null)
                backgroundImage.color = bgOriginalColor;
        }

        // Boss fade
        if (isBossBar && bossGroup != null)
        {
            float targetAlpha = bossVisible ? 1f : 0f;
            bossGroup.alpha = Mathf.Lerp(bossGroup.alpha, targetAlpha, bossFadeSpeed * Time.deltaTime);
        }
    }

    private void LateUpdate()
    {
        if (billboardToCamera)
        {
            Camera cam = Camera.main;
            if (cam != null)
                transform.forward = cam.transform.forward;
        }
    }

    /// <summary>
    /// Update health fill. Connect to HealthSystem.OnHealthChanged.
    /// </summary>
    public void SetHealth(float current, float max)
    {
        if (max <= 0f) return;

        float previous = targetFill;
        targetFill = Mathf.Clamp01(current / max);

        if (targetFill < previous)
        {
            // Start delayed damage
            delayedTimer = delayedDelay;

            // Flash
            if (backgroundImage != null)
            {
                backgroundImage.color = flashColor;
                flashTimer = flashDuration;
            }
        }
        else if (targetFill > previous)
        {
            delayedFill = targetFill;
        }
    }

    /// <summary>
    /// Set health instantly without smooth transition.
    /// </summary>
    public void SetHealthImmediate(float current, float max)
    {
        if (max <= 0f) return;
        targetFill = Mathf.Clamp01(current / max);
        currentFill = targetFill;
        delayedFill = targetFill;

        if (fillImage != null)
        {
            fillImage.fillAmount = currentFill;
            fillImage.color = healthGradient.Evaluate(currentFill);
        }
        if (delayedFillImage != null)
            delayedFillImage.fillAmount = delayedFill;
    }

    /// <summary>
    /// Update shield amount. Shield is displayed on top of health.
    /// </summary>
    public void SetShield(float current, float max)
    {
        if (shieldContainer != null)
            shieldContainer.SetActive(current > 0f);

        if (shieldFillImage != null && max > 0f)
        {
            shieldFill = Mathf.Clamp01(current / max);
            shieldFillImage.fillAmount = shieldFill;
        }
    }

    /// <summary>
    /// Configure boss mode: set name and number of phases.
    /// </summary>
    public void SetBossMode(string bossName, int phases)
    {
        isBossBar = true;

        if (bossNameText != null)
            bossNameText.text = bossName;

        // Create phase markers
        if (phaseMarkerContainer != null && phaseMarkerPrefab != null)
        {
            foreach (Transform child in phaseMarkerContainer)
                Destroy(child.gameObject);

            for (int i = 1; i < phases; i++)
            {
                GameObject marker = Instantiate(phaseMarkerPrefab, phaseMarkerContainer);
                RectTransform rt = marker.GetComponent<RectTransform>();
                if (rt != null)
                {
                    float xPos = (float)i / phases;
                    rt.anchorMin = new Vector2(xPos, 0f);
                    rt.anchorMax = new Vector2(xPos, 1f);
                    rt.anchoredPosition = Vector2.zero;
                    rt.sizeDelta = new Vector2(2f, 0f);
                }
            }
        }
    }

    /// <summary>Show the boss health bar with fade-in.</summary>
    public void ShowBossBar()
    {
        bossVisible = true;
    }

    /// <summary>Hide the boss health bar with fade-out.</summary>
    public void HideBossBar()
    {
        bossVisible = false;
    }

    /// <summary>Set portrait sprite at runtime.</summary>
    public void SetPortrait(Sprite sprite)
    {
        if (portraitImage != null)
        {
            portraitImage.sprite = sprite;
            portraitImage.gameObject.SetActive(sprite != null);
        }
    }

    private void GenerateSegments()
    {
        if (!showSegments || segmentContainer == null || segmentLinePrefab == null) return;
        if (segmentCount <= 1) return;

        foreach (Transform child in segmentContainer)
            Destroy(child.gameObject);

        for (int i = 1; i < segmentCount; i++)
        {
            GameObject line = Instantiate(segmentLinePrefab, segmentContainer);
            RectTransform rt = line.GetComponent<RectTransform>();
            if (rt != null)
            {
                float xPos = (float)i / segmentCount;
                rt.anchorMin = new Vector2(xPos, 0f);
                rt.anchorMax = new Vector2(xPos, 1f);
                rt.anchoredPosition = Vector2.zero;
                rt.sizeDelta = new Vector2(1f, 0f);
            }
        }
    }
}