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

# Bind Variables

Name a source once, use the name everywhere.

A direct object reference is the wrong tool in three common cases: a prefab that must work in several scenes, an object spawned at runtime, and a value that fifty bindings all need to agree on.

A **bind variable** is a named value. Bindings refer to the name; something else decides what the name points at.

{% hint style="success" %}
**Learn by doing:** [A Settings Panel That Drives the Scene](/binding-system-3/tutorials/settings-panel.md) creates a variable from a bind row, fans it out to three targets, and drives them at edit time.
{% endhint %}

## The idea

```
50 bindings  →  variable "player"  →  the Player GameObject in this scene
```

Change the variable, and all fifty bindings follow. Ship the prefab to another scene, give that scene its own `player`, and nothing in the prefab needs editing.

A variable can hold anything serializable, not only objects: a `float` threshold, a `Color` for a theme, a `Vector3` spawn offset, a `ScriptableObject`, an enum.

## The two containers

<table><thead><tr><th width="270">Container</th><th width="140">Scope</th><th>Where it lives</th></tr></thead><tbody><tr><td><strong>Scene variables</strong></td><td>One scene</td><td>A component in the scene. Available to every binding in that scene. Created on demand.</td></tr><tr><td><strong>Variable asset</strong></td><td>The project</td><td>A <code>ScriptableObject</code>. Available to bindings in every scene, and it persists across scene loads.</td></tr></tbody></table>

Create an asset with **Assets ▸ Create ▸ Binding System ▸ Bind Variables Asset**, or from the button in the Bind Variables window.

Scene variables live on a hidden GameObject, because nothing in the scene should depend on where it sits. Reveal it with [Visualization ▸ Scene Variables Object](/binding-system-3/reference/settings.md#visualization) if you need to inspect or move it.

{% hint style="info" %}
When two containers define the same variable name, the one registered later wins. In practice that means a scene variable overrides a project asset variable of the same name, which is usually what you want: a per-scene override of a global default.
{% endhint %}

## The Bind Variables window

**Window ▸ Binding System ▸ Bind Variables**, or **Project Settings ▸ Binding System ▸ Tools**.

Every container in the project, scene components and assets alike, listed in one place with their variables editable inline. Search across all of them, undo and redo like any Inspector edit, select a container to jump to it, or remove one.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FCVrhaKfTwGPvNSQyBmAX%2FScreenshot%202026-09-27%20at%2023.29.44.png?alt=media&amp;token=432af0ed-3da7-4003-9e0e-bfc908b566c7" alt="" width="563"><figcaption><p>The Bind Variables window</p></figcaption></figure>

## A variable's fields

<table><thead><tr><th width="200">Field</th><th>What it is for</th></tr></thead><tbody><tr><td><strong>Name</strong></td><td>What bindings refer to. The window warns you when a name is already used in another container.</td></tr><tr><td><strong>Type</strong></td><td>Any serializable type. Object types are stored as a reference; everything else is serialized to JSON. The picker opens on the types a variable nearly always is (numbers, strings, vectors, colours, GameObject and the common components), with the rest under <strong>In This Project</strong>, <strong>In Unity</strong> and <strong>In .NET</strong>, each read only when you open it. Types are named as you would write them in C#: <code>float</code> rather than <code>Single</code>.</td></tr><tr><td><strong>Value</strong></td><td>Edited with the normal editor field for the type. Types with no built-in field say so rather than showing a broken control.</td></tr><tr><td><strong>Scope</strong></td><td>A free-text group. Purely organisational: it groups the variable in the bind path menu and in the window.</td></tr><tr><td><strong>Description</strong></td><td>A note for whoever meets this variable in the bind path menu.</td></tr><tr><td><strong>On Value Change</strong></td><td>Targets this variable writes to whenever its value changes. Empty on most variables. See <a href="#on-value-change">On Value Change</a>.</td></tr></tbody></table>

Each variable also carries a stable id that does not change when you rename it, so **renaming a variable does not break the bindings that use it**.

## Using one in a binding

Two routes, both ending in the same place:

{% tabs %}
{% tab title="From the bind path menu" %}
Open the bind path menu on an empty binding and look under **Other Sources ▸ From Variable**. Variables are grouped by scope, and only variables that can actually feed this field are listed.

* A variable holding a plain value is a leaf: its own value is the thing you bind.
* A variable holding an object opens into that object's members, so you can pick a path through it.

Choosing an entry sets the source mode to **Variable** and the path in one step.
{% endtab %}

{% tab title="From the source view" %}
Open the source view, set the mode dropdown to **Variable**, then pick the variable in the field that appears. Then pick the path from the bind path menu as usual.
{% endtab %}
{% endtabs %}

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FcBBhdbxvVeOCq4WeZk2p%2FScreenshot%202026-09-27%20at%2023.32.50.png?alt=media&amp;token=a1b75263-25f0-49bf-8866-22be0736b866" alt="" width="287"><figcaption><p>From Variable in the bind path menu</p></figcaption></figure>

## On Value Change

Everything above is a binding pulling from a variable: the variable sits still, and whatever needs the value reads it. A variable can also push.

Every variable carries a list of **On Value Change** targets. Each one is a write-only binding, and the moment the variable's value changes the new value is written through all of them. Nothing subscribes, nothing polls, and the objects being driven need no binding of their own.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FFIIpBwGlLWrP2sx9O3BG%2FScreenshot%202026-09-27%20at%2023.38.34.png?alt=media&amp;token=dc0743a8-dcd4-4f88-b9ec-eb9074718e76" alt=""><figcaption><p>A variable with two On Value Change targets</p></figcaption></figure>

Open the list from the small counter pill on the variable's row. It carries the number of targets, so a variable that drives something says so without being opened. **Add Target** appends a row, and that row is the bind row you already know: pick an object, pick a path, and add [converters](/binding-system-3/pipeline/converters.md) and [modifiers](/binding-system-3/pipeline/modifiers.md) if the target does not take the variable's type as it is.

Only the direction is fixed. A target always writes, so the mode is locked and the bind path menu offers writable members only.

### Push or pull

Both routes end with the same value in the same place. The difference is where the wiring lives and when it runs.

<table><thead><tr><th width="230">Question</th><th width="240">Pull (a binding sourced from the variable)</th><th>Push (On Value Change)</th></tr></thead><tbody><tr><td>Where is the wiring?</td><td>On each object that reads. Scattered, but each object owns its own.</td><td>On the variable. Gathered, and the objects stay untouched.</td></tr><tr><td>When does it run?</td><td>At its update point, whether or not the value moved.</td><td>Only when the value actually changes.</td></tr><tr><td>Does it work with the game stopped?</td><td>Yes, with the <strong>EDITOR</strong> update point.</td><td>No, play mode only.</td></tr><tr><td>Can it read back?</td><td>Yes. A binding can be <code>RW</code> against a variable.</td><td>No. A target only ever writes.</td></tr></tbody></table>

A value that changes rarely and drives a lot, a difficulty setting or a theme colour, is the case push is for: fifty pulling bindings would re-read an unchanged value every frame. A value that changes constantly, or a target that also has to write back, is the case pull is for.

### When it writes

* Whenever something sets the value: `BindVariablesSystem.SetVariableValue` from code, or a binding writing back into the variable.
* While playing, whenever you edit the value in the Bind Variables window or on the container itself.

Editing the value with the game stopped does not push. The write would reach scene objects with nothing recorded for undo, which is not what typing into a field should do. A binding that writes into the variable at the **EDITOR** update point still pushes, because that binding was asked to run at edit time.

### On Activate

Each target carries an inline switch, **On Activate**, on by default.

A change-driven write reaches only the targets that are there to hear it, which leaves two gaps: the first frame of a session, where the value has not changed yet, and the object switched on, spawned or loaded after the last change. **On Activate** closes both. With it on, the value is also written once as soon as the variable's container and the target are both active.

Active means what it means for the target: a GameObject active in the hierarchy, a Behaviour that is enabled and whose GameObject is active. A target with no active state of its own, an asset, a `ScriptableObject`, a material, is always ready, so it is written once at startup. A target that is off is written the moment it turns on, once per activation and no more.

Turn the switch off and the target hears about changes only, keeping whatever it was authored with until the variable next moves. That is what you want when the target has a meaningful starting value of its own.

{% hint style="info" %}
A variable with no targets costs nothing: setting its value checks a count and returns. The per-frame watch that notices a target turning on is registered only while at least one target in the project has **On Activate** on, and drops out again when none does.
{% endhint %}

## From code

```csharp
using Postica.BindingSystem;

// Read a variable
if (BindVariablesSystem.TryGetVariable(variableId, out var variable))
    Debug.Log(variable.Value);

// Write one. Every binding sourced from it follows.
BindVariablesSystem.SetVariableValue(variableId, 42f);

// Enumerate everything available
foreach (var v in BindVariablesSystem.GetAllVariables())
    Debug.Log($"{v.Scope}/{v.VariableName} : {v.Type.Name}");
```

Both take the variable's **id**, not its name. The id is a GUID that does not change when the variable is renamed, which is what lets a rename leave every binding working. `SetVariableValue` throws `KeyNotFoundException` for an unknown id, and `ArgumentException` when `T` is not the declared type. To go from a name to a variable, filter `GetAllVariables()` on `VariableName` once and keep what you find. See [Changing a variable from code](/binding-system-3/overview/sources.md#changing-a-variable-from-code).

Register your own container to feed variables from anywhere, a save file, a remote config, a test fixture:

```csharp
public class MyVariableSource : MonoBehaviour, IBindVariableContainer
{
    public bool IsAlive => this;

    public IEnumerable<IBindVariable> GetAllVariables() => _myVariables;

    public bool TryGetVariable(string variableId, out IBindVariable variable) { /* ... */ }

    void OnEnable()  => BindVariablesSystem.RegisterContainer(this);
    void OnDisable() => BindVariablesSystem.UnregisterContainer(this);
}
```

Containers registered later have higher priority. Registering a container that is already registered moves it to the end, giving it the highest priority. Unregistering is good hygiene but not strictly required: containers whose object is gone are dropped automatically.

## Notes

* Variable resolution is a dictionary lookup, cached per frame. It is one of the cheap [source modes](/binding-system-3/overview/sources.md#cost).
* A variable that does not exist is reported on the bind row: *Variable 'x' not found*. A variable of the wrong type is reported too, naming both types.
* Scene variables run at execution order **-32000**, so they are registered before anything reads them.
* An **On Value Change** target is an ordinary serialized binding, so the [Validator](/binding-system-3/project-tools/diagnostics/validator.md) resolves and reports it like any other, naming the field *On Value Change*.
* Everywhere the editor prints a binding's path, a variable source shows the variable's **name**, not the id stored with it. The [Scene Visualizer](/binding-system-3/project-tools/diagnostics/scene-visualizer.md) also draws these bindings dashed, with a diamond in place of the object that is not there.
* To see **which objects share a variable**, turn the [Scene Visualizer](/binding-system-3/project-tools/diagnostics/scene-visualizer.md#shared-source-points) on: every binding reading one variable is drawn from a single point in the scene, so the arrows that meet are the objects on the same variable. Hovering that point names them; clicking it selects them.

## Related pages

* [Sources](/binding-system-3/overview/sources.md): the Variable mode among the other seven.
* [Pinned Paths](/binding-system-3/overview/pinning.md): the other way to make a source reusable, by path rather than by name.
* [State and State Asset](/binding-system-3/overview/state.md): values that belong to one object or one asset, reached through it rather than by name.
* [Scene Visualizer](/binding-system-3/project-tools/diagnostics/scene-visualizer.md#shared-source-points): how a variable binding is drawn in the Scene view, and how to see everything reading one variable at once.
* [Settings](/binding-system-3/reference/settings.md#visualization): revealing the scene variables object.
