Progress Bar family — one shared animation core (ProgressBarData + ProgressBarController), several shapes on top. This page shows a shader-driven build; siblings: Overview · Linear · Radial · Architecture.

This example uses the Progress Bar component with a custom Shader Graph material for enhanced visuals. For creating fully custom progress bar types, see Architecture.

This demo showcases an RPG-style health bar with custom shader effects for enhanced visual appeal.

Overview

The RPG Demo demonstrates how to combine Luna's Progress Bar system with custom shaders to create visually stunning health bars commonly seen in RPG games. This approach allows for effects that go beyond what's possible with USS styling alone.

Features

  • Shader-based rendering — Custom visual effects like gradients, glows, and animated patterns
  • Smooth animations — Luna's built-in animation system for value transitions
  • Indicator preview — Shows damage/heal amounts before applying
  • Customizable appearance — Full control over colors, effects, and timing

Implementation

Step 1: Create the Shader Graph

Create a custom UI shader using Shader Graph for the progress bar fill.

  1. Create new Shader Graph: Right-click in Project → Create → Shader Graph → URP → Sprite Unlit (or Canvas for UI)

  2. Add properties to the Blackboard:

    • Fill (Float, Range 0-1) — Controls fill amount
    • MainColor (Color) — Base fill color
    • GlowColor (Color) — Edge glow color
    • GlowIntensity (Float) — Glow strength
  3. Build the graph:

    • Use UV node's X channel to compare against Fill property
    • Use Step or Smoothstep for the fill cutoff
    • Calculate edge glow by measuring distance from fill edge
    • Multiply colors and combine outputs

Example node setup:

UV (X) ──→ Step ──→ Multiply ──→ Base Color Fill UV (X) ──→ Subtract (Fill) ──→ Abs ──→ One Minus ──→ Saturate ──→ Multiply (GlowColor) ──→ Add to Base
  1. Save and create Material from the shader

Step 2: Connect the Material to the Progress Bar

The shipped RPGDemo sample (Samples~/Showcase/Components/RPGDemo) does not paint the bar's fill with the material — UI Toolkit USS cannot reference a Material via background-image. Instead, the material lives on a separate orb visual, and the ProgressBar drives the material's shader properties through a custom node updater (see Step 3). The bar itself stays purely a value/animation driver.

If you need direct access to the bar's fill elements (for example, to clear their default background so only the shader visual shows), query them through the component API:

csharp
// Strongly-typed access to the fill elements var fill = progressBar.ProgressElements[0]; // Or query by the actual USS class var fillByClass = progressBar.Q(className: "ProgressBar__progress-element");

Step 3: Sync Shader with Progress Bar

The ProgressBar pushes every animated value through an IProgressBarNodeUpdater. Implement one that writes the values into the material's shader properties, then wire it with SetNodeUpdater(). This mirrors the shipped sample's HealthOrbProgressBarNodeUpdater (Samples~/Showcase/Components/RPGDemo/HealthOrb/HealthOrbProgressBarNodeUpdater.cs).

csharp
using UnityEngine; using UnityEngine.UIElements; using CupkekGames.Luna; // Routes animated progress/indicator values into shader properties. // Adjust the property references to match your Shader Graph // (e.g., "_Fill" if you named the property Fill in Step 1). public class HealthOrbProgressBarNodeUpdater : IProgressBarNodeUpdater { private readonly Material _material; private readonly string _progressProperty; private readonly string _indicatorProperty; public HealthOrbProgressBarNodeUpdater(Material material, string progressProperty = "_Progress", string indicatorProperty = "_Progress_Indicator") { _material = material; _progressProperty = progressProperty; _indicatorProperty = indicatorProperty; } public void SetValue(ProgressBarNodeType nodeType, int index, VisualElement element, float value) { UpdateValue(nodeType, index, element, value); } public void UpdateValue(ProgressBarNodeType nodeType, int index, VisualElement element, float value) { if (_material == null) return; if (nodeType == ProgressBarNodeType.Indicator) { _material.SetFloat(_indicatorProperty, value); } else { _material.SetFloat(_progressProperty, value); } } public void UpdatePosition(ProgressBarNodeType nodeType, int index, VisualElement element, float position) { } public void SetColor(ProgressBarNodeType nodeType, int index, VisualElement element, Color color) { } public void OnAnimationStart(ProgressBarNodeType nodeType, int index, VisualElement element, bool positive) { } public void OnAnimationComplete(ProgressBarNodeType nodeType, int index, VisualElement element, bool positive) { } }
csharp
using UnityEngine; using UnityEngine.UIElements; using CupkekGames.Luna; public class RPGHealthBarController : MonoBehaviour { [SerializeField] private Material _healthBarMaterial; private PanelRenderer _panelRenderer; private ProgressBar _healthBar; private void Awake() { // PanelRenderer delivers the visual tree asynchronously — // register a reload callback and query elements when it fires. _panelRenderer = GetComponent<PanelRenderer>(); if (_panelRenderer != null) { _panelRenderer.RegisterUIReloadCallback(OnUIReload); } } private void OnUIReload(PanelRenderer renderer, VisualElement root, int version) { if (_healthBar != null) return; // sentinel: only init once _healthBar = root.Q<ProgressBar>("health-bar"); // Route every animated value into the material's shader properties _healthBar.SetNodeUpdater(new HealthOrbProgressBarNodeUpdater(_healthBarMaterial)); } private void OnDestroy() { if (_panelRenderer != null) { _panelRenderer.UnregisterUIReloadCallback(OnUIReload); } } public void TakeDamage(float amount) { _healthBar.Progress[0].TargetValue -= amount; _healthBar.Indicator.TargetValue = _healthBar.Progress[0].TargetValue; _healthBar.PlayProgress(); _healthBar.PlayIndicator(); } }

Tips

Performance

  • Use MaterialPropertyBlock for per-instance shader properties when you have multiple health bars
  • Consider using a shared material for all health bars of the same type

Visual Polish

  • Add subtle animation to the glow effect using _Time in the shader
  • Use the indicator bar to show incoming damage with a different color
  • Add particle effects for critical hits or healing

Accessibility

  • Ensure sufficient contrast between filled and empty states
  • Consider adding numerical display alongside the bar
  • Test with colorblind-friendly palettes

See also

Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL