> 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/bind-field.md).

# When to Declare a Bind Field

The same health bar as a Bind\<T> field, and when to choose it.

The documentation leads with proxy bindings because they are right most of the time. This tutorial is about the times they are not.

You will build the same health bar twice, once as a `Bind<float>` field in your own code, then make the difference between the two visible as a number on screen rather than as an argument.

## What you will build

A health bar driven by a bind field, and a two-line readout proving that the two mechanisms deliver their value at different moments in the frame.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2F1jZp03GfxLu2MeK2w8D1%2FScreenshot%202026-09-26%20at%2023.02.18.png?alt=media&amp;token=fe268fa7-e257-44a6-b24f-94439b50424f" alt="" width="563"><figcaption><p>The same value, read two ways, one frame apart</p></figcaption></figure>

**About 20 minutes.** Programmers only: this one is all code.

## What you need

A project with the Binding System, and somewhere to put two small scripts.

## 1. The health bar as a bind field

Create `HealthBar.cs`:

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

```csharp
using Postica.BindingSystem;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Image))]
public class HealthBar : MonoBehaviour
{
    public Bind<float> fill = 0f.Bind();   // .Bind() gives the field a starting value

    private Image _image;

    private void Awake() => _image = GetComponent<Image>();

    private void Update() => _image.fillAmount = fill;   // implicit conversion, no .Value
}
```

{% endcode %}

{% hint style="info" %}
Even though this script references another component and against what the tool is aiming to fix, it is here only for the sole purpose of having a similar behaviour to what was done in previous tutorials and have a fair comparison.
{% endhint %}

Put it on a filled `Image`, as in [the first tutorial](/binding-system-3/tutorials/health-bar.md): Source Image set, Image Type **Filled**, Fill Method **Horizontal**.

In the Inspector, `fill` draws as a plain float with a small hexagon toggle to its left. Click the hexagon.

The field becomes the same bind row you have used all along. Drag a health source onto it and pick the value, then add a **Normalize Value** modifier the same way.

Press play. It works, exactly like the proxy version.

## 2. Notice what is not there

Open the bind menu on this row and look for **Update Points**. It is not there.

Instead, under **Settings**, there is a single **Auto Update** toggle.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FbVPqFHtAnDl2AveQwx42%2FScreenshot%202026-09-26%20at%2022.54.03.png?alt=media&amp;token=c7c85e55-2615-4c34-9c55-ab04792270e2" alt="" width="563"><figcaption><p>The bind menu on a Bind field: Auto Update, and no update points</p></figcaption></figure>

That is the whole difference, and it is not a missing feature. Your `Update` reading `fill` **is** the update. There is no stage to choose because you already chose it by putting the read where you put it.

Which means you also wrote a class to do it. That is the trade, stated plainly: a line of code and a `MonoBehaviour`, in exchange for deciding the exact moment the value is read.

## 3. Make the difference visible

Arguing about a frame of latency is unproductive. Print it instead.

Create `Ticker.cs`:

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

```csharp
using UnityEngine;

public class Ticker : MonoBehaviour
{
    public int frame;

    private void Update() => frame++;
}
```

{% endcode %}

And `Readout.cs`:

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

```csharp
using Postica.BindingSystem;
using UnityEngine;

public class Readout : MonoBehaviour
{
    public Ticker ticker;

    public Bind<int> viaBindField;   // bind this to Ticker.frame
    public int viaProxyBinding;      // proxy-bind this to Ticker.frame

    private void LateUpdate()
    {
        Debug.Log($"actual {ticker.frame} | bind field {viaBindField} | proxy {viaProxyBinding}");
    }
}
```

{% endcode %}

Put `Ticker` on one GameObject and `Readout` on another, and assign `ticker`.

Now wire both fields to the same source:

* Click the hexagon beside **Via Bind Field** and bind it to **Ticker ▸ frame**.
* Right click the **Via Proxy Binding** label, choose **Enable Binding**, click the hexagon, and bind it to **Ticker ▸ frame** as well. Leave the update point at **UPDATE**, which is where it starts.

Two mechanisms, one source, one log line.

## 4. Read the log

Press play. Every line looks like this:

```
actual 137 | bind field 137 | proxy 136
```

The proxy binding is exactly one frame behind, every frame, forever.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2Fi2DJyTeHpNHDzV4EbgTA%2FScreenshot%202026-09-26%20at%2022.59.08.png?alt=media&amp;token=4dced26d-14b6-48e8-8834-259c82cd9f7e" alt="" width="563"><figcaption><p>The same source read two ways, one frame apart</p></figcaption></figure>

Nothing is broken. The **UPDATE** point means *read before `Update` runs*, and `Ticker.Update` is what increments the number, so the proxy captured the value from before this frame's increment. The bind field was read inside `LateUpdate`, after `Ticker.Update` had already run, so it saw the new one.

## 5. Fix it, and notice what the fix costs

Open the bind menu on **Via Proxy Binding**, go to **Update Points ▸ Update On**, untick **UPDATE** and tick **LATE UPDATE**.

Press play again:

```
actual 137 | bind field 137 | proxy 137
```

They agree. The proxy now reads before `LateUpdate`, which is after `Ticker.Update`, which is where the bind field was reading.

So the latency was never a property of proxy bindings. It was a scheduling choice, and you just made a different one. The real difference is that you had to know to make it, and that there are only so many stages to choose from. A bind field has infinite stages, because the stage is wherever you wrote the read.

## 6. The case a bind field actually wins

Both of the above are ties. Here is the one that is not.

Add this to `Readout`:

```csharp
public Bind<int> onDemand;    // bind to Ticker.frame

public void Report() => Debug.Log($"asked at {Time.frameCount}, got {onDemand}");
```

Bind `onDemand` and call `Report()` from a button, or from a key press. The value it reports is the value **at the moment you asked**, and between calls the binding costs nothing at all: no engine registration, no per-frame read, no work.

A proxy binding cannot do this. It has to pick a stage and run there, whether or not anyone wants the value. For a value read once a second, or once per level, you have paid for the binding and thrown the work away.

{% hint style="info" %}
This is the same reason a bind field has no **EDITOR** update point. Nothing is scheduled, so there is nothing to schedule at edit time. Auto Update itself does nothing outside play mode.
{% endhint %}

## 7. When you want it automatic anyway

Sometimes the value has to move without anyone reading it: a modifier has to keep advancing, or something downstream has to be told the value changed.

**Auto Update**, in the bind menu under **Settings**, registers the field with the engine and refreshes it **once per frame, between `Update` and `LateUpdate`**. One stage, no interval, no choice, because the point of a bind field is that your code decides.

Subscribing to `ValueChanged` registers the same way, implicitly, because otherwise nothing would be watching:

```csharp
private void OnEnable()  => health.ValueChanged += OnHealthChanged;
private void OnDisable() => health.ValueChanged -= OnHealthChanged;

private void OnHealthChanged(int oldValue, int newValue) { /* ... */ }
```

## What just happened

**Two mechanisms, one pipeline.** Sources, paths, converters, modifiers, Live Debug and Path Value Preview are identical on both. Everything in this documentation about the pipeline applies to both unchanged. The difference is entirely about *when the value moves*, and nothing else.

**A proxy binding is engine-driven; a bind field is caller-driven.** The engine reads a proxy binding at the stage you selected, before your `Update` and after it for writes. A bind field is resolved lazily on first access and then read whenever you read it. See [Bind Modes and Update Points](/binding-system-3/overview/modes-and-updates.md).

**Auto Update is one fixed stage.** It is inserted into the player loop between `Update` and `PreLateUpdate`, and it is skipped entirely when the game is not playing. It is not a smaller version of update points; it is the single case where a bind field needs the engine at all.

**The accessor is shared.** Both mechanisms build the same cached accessor, keyed by type and path, so neither is doing reflection per frame. Performance differences between them come from *how often the read happens*, not from how the read works. [Binding from Code](/binding-system-3/overview/code.md) covers the accessor layer directly. In a hot loop you will want it.

## Which mode is right

<table><thead><tr><th width="330">Situation</th><th width="150">Use</th><th>Why</th></tr></thead><tbody><tr><td>You are not sure</td><td><strong>Proxy</strong></td><td>Being wrong costs one click.</td></tr><tr><td>The field is not yours: Unity, a package, a plugin</td><td><strong>Proxy</strong></td><td>The only option.</td></tr><tr><td>A shader property, or a field on an asset</td><td><strong>Proxy</strong></td><td>The only option.</td></tr><tr><td>A designer has to be able to rewire it</td><td><strong>Proxy</strong></td><td>No source file, no recompile, no programmer.</td></tr><tr><td>You want to see it work while authoring</td><td><strong>Proxy</strong></td><td>Only proxy bindings have an <strong>EDITOR</strong> update point.</td></tr><tr><td>The value has to follow animation exactly</td><td><strong>Either</strong></td><td>Proxy on <strong>LATE UPDATE</strong>, or a bind field read in <code>LateUpdate</code>.</td></tr><tr><td>Your own code already reads it every frame</td><td><code>Bind&#x3C;T></code></td><td>The read is the update, so it is free.</td></tr><tr><td>The value is needed rarely, or on demand</td><td><code>Bind&#x3C;T></code></td><td>Costs nothing between reads. A proxy would run all frame, every frame.</td></tr><tr><td>The value must be exact at a precise line of code</td><td><code>Bind&#x3C;T></code></td><td>Read it on that line.</td></tr><tr><td>You need <code>ValueChanged</code> in C#</td><td><code>Bind&#x3C;T></code></td><td>The event lives on the field.</td></tr><tr><td>The object is spawned and wired at runtime</td><td><code>Bind&#x3C;T></code></td><td><code>new Bind&#x3C;float>(target, "position.x")</code>.</td></tr></tbody></table>

## Try changing this

**Make it read-only.** Change the field to `ReadOnlyBind<float>`. The mode icon is fixed at `R` and the bind path menu only offers members that can be read. Use it for anything your code never writes: it documents the intent and removes a whole class of mistake. `Bind<T>` converts to `ReadOnlyBind<T>` implicitly, so passing one to a method that takes the read-only form just works.

**Delete the read.** Comment out the `Update` in `HealthBar`. The binding is still configured, still valid, and the bar never moves. That is the failure mode of this mode, and it is worth seeing once so you recognise it later.

**Turn Auto Update on and delete the read.** Now the field refreshes anyway, once per frame, and you have reinvented a proxy binding with more code and fewer stage options. Which is the honest summary of when not to use a bind field.

**Bind it from code instead of the Inspector.** Replace the field's Inspector wiring with `fill = new Bind<float>(healthTransform, "position.x")` in `Start`. The same pipeline, built at runtime. Spawned objects and tests need exactly this.

## Related pages

* [Two Ways to Bind](/binding-system-3/overview/two-ways-to-bind.md): the same comparison, without building anything.
* [Binding from Code](/binding-system-3/overview/code.md): the full API, runtime construction, and raw accessors.
* [Bind Types](/binding-system-3/overview/bind-types.md): `ReadOnlyBind`, `WriteOnlyBind`, the lite variants, and noticing a change.
* [Bind Modes and Update Points](/binding-system-3/overview/modes-and-updates.md): stages, intervals, and Auto Update.
* [A Health Bar with No Driver Class](/binding-system-3/tutorials/health-bar.md): the same bar, the other way.
