Part of these game systems:
Health Bar UI Pro
Advanced health bar with delayed damage indicator, shield overlay, boss mode with phase markers, and segmented health display.
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
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);
}
}
}
}