> For the complete documentation index, see [llms.txt](https://postica.gitbook.io/binding-system-3/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://postica.gitbook.io/binding-system-3/tutorials/health-bar.md).

# A Health Bar with No Driver Class

A UI fill bar that follows a health value, with no driver script.

Nearly every project has a small class whose whole job is one line in `Update`: read a number, divide it by another number, push the result into `Image.fillAmount`. This tutorial builds the same bar without that class, and then makes it survive a change to the maximum.

## What you will build

A horizontal fill bar that tracks a health value, rescales itself when the maximum changes, and does not overflow when the character is overhealed.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FpvEnxZqf8CI4ZevcYFxB%2Fimage.png?alt=media&amp;token=cab66ca0-9d37-4050-9140-86ed0fc5cd69" alt=""><figcaption><p>The finished bar, following a health value with no driver script in the project</p></figcaption></figure>

**About 10 minutes.**

## What you need

A project with the Binding System installed, and any component holding a current value and a maximum. Step 2 provides one if you do not have it yet.

## 1. Make an Image that can be filled

Create **GameObject ▸ UI ▸ Image** and name it `HealthFill`. Unity adds a Canvas and an EventSystem if the scene has none.

In the Inspector, set **Source Image** to `UISprite`, or any sprite of your own.

{% hint style="warning" %}
Do this before anything else. Unity's Image inspector hides the **Image Type** dropdown entirely while Source Image is empty, and **Fill Amount** lives under it. If you cannot find Fill Amount, this is why.
{% endhint %}

Now set:

* **Image Type** to **Filled**
* **Fill Method** to **Horizontal**
* **Fill Origin** to **Left**

A **Fill Amount** slider appears. Drag it and the image grows and shrinks from the left edge. That slider is the thing you are about to stop writing code for.

## 2. Put the health value somewhere

If you already have a component with a current and a maximum, skip this step and use it.

Otherwise, create `Health.cs` and add it to any GameObject in the scene. Call that object `Player`.

{% code title="Health.cs" %}

```csharp
using UnityEngine;

public class Health : MonoBehaviour
{
    public float current = 100f;
    public float max = 100f;
}
```

{% endcode %}

Notice what is not in this file: no reference to the Image, no reference to the Binding System, no `Update`. It is data. That is the whole point of the exercise.

## 3. Turn Fill Amount into a binding

Right click the **Fill Amount** label on the Image and choose **Enable Binding**.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FounwyAwBAFMU1CuVjCbE%2FScreenshot%202026-09-26%20at%2013.51.12.png?alt=media&amp;token=de1285d0-b9d8-40fd-85e9-7f3867c3e751" alt="" width="563"><figcaption><p>Enable Binding on Image.fillAmount</p></figcaption></figure>

The slider does not change yet. A small **hexagon toggle** appears beside the label. Click it.

*Now* the slider is replaced by a bind row: a source slot on the left and a path button reading **NOTHING SELECTED**.

Nothing was written to a script and nothing recompiled. The hexagon switches the binding off and back on without losing its setup, and right clicking again offers **Disable Binding**, which removes it altogether and puts the slider back with its value intact.

## 4. Point it at the health value

Drag the `Player` GameObject from the Hierarchy and drop it on the bind row.

Two things happen at once. The source slot fills, and the bind path menu opens by itself, already filtered to members that can produce a `float`.

Type `current` in the search box and choose **Health ▸ current**.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FE7WT0kwDnbY7C9WRu6Yh%2FScreenshot%202026-09-26%20at%2013.53.58.png?alt=media&amp;token=17d7acc0-0478-4bbb-9fda-e8fa5b1995cc" alt=""><figcaption><p>Fill Amount, bound to Health.current</p></figcaption></figure>

The path button now reads `Current`.

## 5. Check the value before pressing play

On the path button there is a row of small toggles. Switch on the one whose tooltip reads *Preview data pointed by the path*.

A field appears under the row showing `100`. That is the live value at the end of the path, read at edit time.

Type `50` into it and press Enter. The number in `Health.current` on the Player changes to 50, because the preview field writes back through the path to the source. Undo works normally.

Leave it at 50. You will want it there in step 7.

## 6. Confirm when it updates

Open the bind menu, then choose **Update Points ▸ Update On**.

**UPDATE** should already be lit, along with **Optimized Update**. New bindings start from the project's default bind data, and out of the box that default is UPDATE with Optimized Update on.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FdCS0ruPqhcDVW60MkmjS%2FScreenshot%202026-09-26%20at%2014.14.17.png?alt=media&amp;token=10b24ff9-770d-4c6b-bd0e-28d542fcf923" alt="" width="563"><figcaption><p>The update points panel, with UPDATE lit</p></figcaption></figure>

{% hint style="warning" %}
**UPDATE** is already lit, because that is the default for a new binding. Confirm it rather than choosing it.

The reason the panel exists at all is that a binding made this way has no moment of its own: the Image does not know it is bound and will never ask for a value, so the engine has to move it. Untick everything and the panel says *This bound field won't be updated automatically*, and it means it exactly.

That empty state is occasionally what you want. It is how you say that your own code, or a `UnityEvent`, decides when the binding runs. See [No update point, on purpose](/binding-system-3/overview/modes-and-updates.md#no-update-point-on-purpose).
{% endhint %}

Close the menu and press play. The bar snaps to **completely full**, even though health is 50 out of 100. That is the next step.

## 7. Fix the range with Normalize Value

`fillAmount` is a 0 to 1 number. It is being handed 50. Unity clamps that to 1, so the bar reads full at any health above 1 point.

The usual fix is the line of arithmetic in the driver class. Instead, put it in the connection.

1. Open the bind menu again.
2. Under **Add Modifier**, choose **Normalize Value**.
3. Leave **Min** at `0`.
4. On the **Max** field, click the small hexagon toggle to its left, then drag the `Player` GameObject onto it and choose **Health ▸ max**.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FtrmFkYzPpumYC5HixAw4%2FScreenshot%202026-09-26%20at%2014.15.35.png?alt=media&amp;token=fd62994e-1411-4663-ade5-ecdce709d6b6" alt="" width="563"><figcaption><p>Normalize Value, with Max itself bound to Health.max</p></figcaption></figure>

Press play. With `current` at 50 and `max` at 100 the bar is **half full**. Drag `current` in the Inspector while the game runs and the bar follows.

Now change `max` to 200, still in play mode. The bar drops to a **quarter** without you touching the Image, the modifier, or any script. The maximum is not a number copied into the bar; it is a second connection.

Set `current` to 400, above the maximum. The bar stays full rather than overflowing, because Normalize Value clamps its result to the 0 to 1 range.

## 8. Watch the value move through the pipeline

Still in play mode, click the debug icon on the bind row. Its tooltip when off reads *Live Debug is Inactive. Click to enable it*. It works with the game stopped as well, but watching the value move is the point here.

The row expands into one line per stage:

* **Path**, the raw `50` that came out of `Health.current`
* **Normalize Value**, the `0.5` it turned into
* **Output**, the value `fillAmount` received

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FXl1nAQB3pivLYaJgg4pV%2FScreenshot%202026-09-26%20at%2014.17.19.png?alt=media&amp;token=3d473944-0f0b-4333-a9f5-77208dcdcbdb" alt="" width="563"><figcaption><p>Live Debug on the health bar binding, stage by stage</p></figcaption></figure>

Drag `current` and watch every stage move together. When a binding one day produces the wrong number, this is the view that tells you which stage did it.

Leave play mode. The rows stay, and the read stages keep reporting: `Health.current` is still there to read, game or no game. Type `0.9` into the **Normalize Value** row and the Output row follows it, in violet, without anything being written to the scene. Click the revert button to release it. That is how you check what the rest of a chain does with a value the source has not produced yet. See [Live Debug](/binding-system-3/project-tools/diagnostics/live-debug.md).

## What just happened

**The Image was never modified.** Enable Binding did not add a field to the Image or touch its serialized data. It added an entry to a hidden `ProxyBindings` component on the `HealthFill` GameObject, keyed by the target object and the serialized property path of `m_FillAmount`. That component is saved in the scene, it is hidden from the Add Component menu, and it runs at execution order -320000 so bindings are in place before anything else in the frame. See [Proxy Bindings](/binding-system-3/overview/proxy-bindings.md) for where the entry is stored when the target is a prefab or an asset instead.

**The path is resolved once, not every frame.** The first time the binding runs, `Health.current` is turned into a direct accessor, cached by type and path. Every later frame is a field read, not a reflection call. Reflection is an authoring-time and first-use cost here, never a per-frame one. [Paths and Parameters](/binding-system-3/overview/paths.md) covers the syntax, and [Sources](/binding-system-3/overview/sources.md) covers the other ways to find the object at the far end.

**UPDATE is a real stage, not a vague promise.** The engine reads the source before `Update` runs and writes results after it, so a value read by your own `Update` is already correct when it gets there. **Optimized Update**, which was on, means the read is skipped entirely on frames where the source is inactive or the value has not changed, which for a health bar is most frames. [Bind Modes and Update Points](/binding-system-3/overview/modes-and-updates.md) lists every stage and the interval controls.

**The modifier is where the arithmetic went.** Normalize Value sits in the read pipeline and maps `[Min, Max]` onto `[0, 1]`, clamping the result. Its Min and Max are not plain floats: they are bind fields in their own right, which is why Max could be pointed at `Health.max` instead of being typed in. That is true of nearly every modifier parameter in the package, and it is the difference between a bar that has a hardcoded 100 in it and a bar that does not care. [Modifiers](/binding-system-3/pipeline/modifiers.md) explains the stack, and the [Modifier Catalogue](/binding-system-3/pipeline/modifier-catalogue.md) lists the other fifty.

**The class you did not write.** A `HealthBar` component would have coupled the Image to `Health`, needed a serialized reference to both, run its `Update` every frame whether or not the value moved, and been unavailable to anyone who does not open source files. The binding does none of that, and a designer can repoint it in two clicks.

## Try changing this

**Delete the Normalize Value modifier.** The bar goes back to reading full at anything above 1 health. Worth doing once, because it makes clear that the modifier is not decoration: without it the two components genuinely disagree about what the number means, and something has to reconcile them.

**Untick UPDATE and tick only EDITOR.** Now the bar follows `current` while you drag it in the Inspector, with the game stopped, which is a pleasant way to author a HUD. Press play and it freezes: EDITOR is an edit-time stage, and no play-mode stage is selected. The update points are literal.

**Set the interval to 4 frames.** In the update points panel, switch the dropdown beside the interval field to **Frames** and enter `4`. The bar now steps rather than glides. On a bar this size the difference is visible; on a numeric label a human reads, it is not, and that is a quarter of the cost. This is the cheapest tuning knob in the system.

## Related pages

* [Proxy Bindings](/binding-system-3/overview/proxy-bindings.md): what Enable Binding creates, and where it is stored.
* [Bind Modes and Update Points](/binding-system-3/overview/modes-and-updates.md): every stage, intervals, and Optimized Update.
* [Modifiers](/binding-system-3/pipeline/modifiers.md): the stack, and why parameters are bindings.
* [Modifier Catalogue](/binding-system-3/pipeline/modifier-catalogue.md): the other fifty.
* [Live Debug](/binding-system-3/project-tools/diagnostics/live-debug.md) and [Path Value Preview](/binding-system-3/project-tools/diagnostics/path-value-preview.md): the two diagnostics used here, and when each applies.
* [TextMeshPro and Unity UI](/binding-system-3/reference/integrations/unity-ui.md): the components this tutorial bound, in more depth.
