> 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/binding-assets.md).

# Binding an Asset You Cannot Edit

Bind a ScriptableObject field and a material property, safely.

Bindings are not only for scenes. A `ScriptableObject` holding your tuning data, a material, a physics material, a render pipeline asset: all of them have serialized fields, and all of them can be bound without a line of code.

The interesting part is not making the binding. It is knowing **where it went**, because that is what shows up in your next commit, and it is different for the two cases in this tutorial.

## What you will build

A tuning asset whose fields are driven by a project-wide variable, and a material whose colour is driven by the same one. Then a short tour of what changed on disk.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FzpiNpkwA7NJlv7YZLYMm%2FScreenshot%202026-09-26%20at%2015.49.02.png?alt=media&amp;token=34567fe6-cde7-48e1-8744-8d32ed62155e" alt=""><figcaption><p>A bound ScriptableObject field and a bound material property, side by side</p></figcaption></figure>

**About 20 minutes.**

## What you need

Any project. A cube with a material on it, and one small `ScriptableObject` type, provided in step 1.

## 1. Get an asset with fields

If you already have a `ScriptableObject` asset, use it and skip ahead. Any will do: a URP pipeline asset, TMP Settings, an input action asset, one of your own.

Otherwise, create `GameTuning.cs`:

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

```csharp
using UnityEngine;

[CreateAssetMenu(menuName = "Tutorial/Game Tuning")]
public class GameTuning : ScriptableObject
{
    public float enemySpeed = 3f;
    public Color hazardColor = Color.red;
}
```

{% endcode %}

Create one with **Assets ▸ Create ▸ Tutorial ▸ Game Tuning** and name it `GameTuning`. As in the other tutorials, there is nothing about binding in that file. It is a container with two fields.

Also put a **Cube** in the scene and give it its own material, so you are not editing the default one. Create it with **Assets ▸ Create ▸ Material**, name it `CubeMat`, and drag it onto the cube.

## 2. Make a project-wide variable to drive them

An asset cannot hold a reference to a scene object, so an asset's bindings need a source that is not in a scene. A **Bind Variables Asset** is the natural one, because it is a project asset itself.

Create it with **Assets ▸ Create ▸ Binding System ▸ Bind Variables Asset** and name it `GameVariables`.

Select it and add one variable:

* **Name** `dangerLevel`
* **Type** `float`
* **Scope** `Tuning`
* **Value** `0.5`

## 3. Bind a field on the ScriptableObject

Select the `GameTuning` asset in the Project window. Its fields appear in the Inspector exactly as a component's would.

Right click **Enemy Speed**, choose **Enable Binding**, and click the **hexagon toggle** that appears beside the label.

Open the source view, set the mode dropdown to **From Variable**, pick `dangerLevel`, and choose **Variable Value** on the path button. Add a **Remap Range** modifier with **Out Min** `1` and **Out Max** `8`, so the 0 to 1 setting becomes a speed.

Open **Update Points ▸ Update On** and tick **UPDATE** and **EDITOR**.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FfUb7haeuVPuWY9mCMP7h%2FScreenshot%202026-09-26%20at%2015.20.39.png?alt=media&amp;token=226346c0-09b5-4a2f-95e8-68f8c0487a70" alt=""><figcaption><p>A bound field on a ScriptableObject asset</p></figcaption></figure>

{% hint style="info" %}
Look at the update points panel here and you will see **ON START** greyed out. It is the one lifecycle point that has no meaning in an asset context: an asset is not in a scene, so there is no `Start` for it to happen in. The others are all available.
{% endhint %}

## 4. Find where that binding went

Nothing in `GameTuning.cs` changed, and the asset gained no new field. So the binding is somewhere.

Open **Project Settings ▸ Binding System**, find **Visualization**, and turn on **Proxy Bindings**.

Now look at the `GameTuning` asset in the Project window and expand its arrow. There is a child object inside it called **Bindings**.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2F8yDXzIIU0Ce61IZS3r7Y%2FScreenshot%202026-09-26%20at%2015.29.23.png?alt=media&amp;token=4ed1cb00-0254-49d7-b654-85b5e0ac76f9" alt="" width="310"><figcaption><p>The Bindings sub-asset revealed inside the ScriptableObject</p></figcaption></figure>

That is a sub-asset: a second object living inside the same `.asset` file. It means the binding travels with the asset. Copy the asset, and the binding is copied. Delete the asset, and the binding goes with it. In version control it appears as a change to `GameTuning.asset` and nothing else, which is exactly where a reviewer would expect to find it.

Disable the last binding on the asset and the sub-asset is removed again, so an asset you experimented with does not keep an empty companion.

## 5. Bind a material property from the cube

Select the **Cube** and scroll to its material section in the Inspector.

Right click the material's **Base Color** swatch (or **Albedo**, depending on your render pipeline) and choose **Enable Binding**.

Click the hexagon and a bind row appears in place of the swatch. Set its source the same way: **From Variable**, `dangerLevel`, **Variable Value**. The variable is a `float` and the property is a `Color`, so a converter is picked for you. Tick **UPDATE** and **EDITOR**.

Drag `dangerLevel` and the cube changes colour in the Scene view.

The binding is not on the material. It is on the **Renderer**, pointing at slot 0 of its shared material array. So it is stored in the scene, on the cube's GameObject, like any other scene binding.

{% hint style="warning" %}
`sharedMaterials` is the material asset itself. A binding driving it at edit time is editing the asset, which is why the change shows up on every object using that material and in your commit as a change to `CubeMat.mat`.

In play mode the binding switches to `materials`, the renderer's own instanced copy, so running the game does not write to the asset. That switch is automatic and applies only when the source is a `Renderer`.
{% endhint %}

## 6. Bind the material asset directly, and watch the storage change

Now do the same thing from the other side.

Select `CubeMat` in the **Project window**, so you are looking at the material asset on its own rather than through a renderer. Right click a different property, for example **Smoothness** or **Metallic**, choose **Enable Binding** and click the hexagon. Give it the same variable source.

This binding has nowhere local to live. A material is not a `ScriptableObject`, so it gets no sub-asset. It goes into the shared **global-bindings** asset instead, at:

```
Assets/Bindings/Resources/global-bindings.asset
```

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FMzMuK9mNcq06T2jghmWq%2FScreenshot%202026-09-26%20at%2015.32.51.png?alt=media&amp;token=05fc9cb9-1d08-41ff-a141-b98bcdf73a30" alt=""><figcaption><p>The global-bindings asset, holding the material binding</p></figcaption></figure>

{% hint style="danger" %}
This file is shared by every bound object that cannot carry a binding of its own. Do not delete it, and expect it to show up in commits whenever anybody adds a binding of this kind. It is also the one storage location that can produce a merge conflict between two people binding unrelated assets, so find out it exists now rather than at merge time.
{% endhint %}

## What just happened

**Three storage locations, one rule.** A binding is kept as close to the bound object as that object allows.

<table><thead><tr><th width="300">The bound object is</th><th>Where the binding is stored</th></tr></thead><tbody><tr><td>A component in a scene or a prefab</td><td>A hidden <code>ProxyBindings</code> component on the same GameObject, saved in the scene or the prefab.</td></tr><tr><td>A <code>ScriptableObject</code></td><td>A hidden <code>Bindings</code> sub-asset inside that asset's own file.</td></tr><tr><td>Any other asset</td><td>The shared <code>global-bindings</code> asset.</td></tr></tbody></table>

The middle row is specifically `ScriptableObject`, not "assets that can hold sub-assets". A material could technically hold one, and does not get one. If you want a binding to travel with an asset rather than sit in the shared file, that is the distinction to plan around. [Proxy Bindings](/binding-system-3/overview/proxy-bindings.md) covers the storage rule in the round.

**An asset binding cannot see a scene.** Unity does not serialize a reference from an asset to a scene object, so an asset's bindings have to resolve their source some other way. A project-scope variable is the usual answer, and the ones that need no reference at all work too: **From Static Value**, and a direct reference to another asset. [Sources](/binding-system-3/overview/sources.md) lists all eight modes and which of them make sense here.

**The material path told you where the binding lives.** `sharedMaterials.Array.data[0]` is a path on the Renderer, not on the material, because you opened the material through the cube. Selecting the material in the Project window gives a binding on the material itself, and a different storage location. Same property, two different bindings, two different files changed.

**Edit time writes the asset, play mode writes the instance.** The `sharedMaterials` to `materials` switch is the reason a bound material property is safe to leave running: play mode changes the renderer's own copy and leaves the `.mat` file alone. At edit time there is no instance, so the asset really is what changes. That cuts both ways: it is what makes the EDITOR update point useful here, and it is what makes it something to be deliberate about.

## Try changing this

**Bind a second field on the same asset.** Bind `hazardColor` to the same variable. No second sub-asset appears: the one `Bindings` object holds the whole list for that asset.

**Point the material binding at the tuning asset instead.** Change the source from the variable to the `GameTuning` asset directly and pick `hazardColor` as the path. Asset to asset, no scene involved, and it works in any scene that loads either one.

**Turn Proxy Bindings visualization back off.** The sub-asset disappears from the Project window and the binding keeps working. The visibility flag is cosmetic: it changes `hideFlags`, not storage. Leaving it on is a good habit while you are learning where things go, and a distraction afterwards.

**Move the Bindings folder.** In **Project Settings ▸ Binding System**, change **Bindings Path**. The `global-bindings` asset moves with it, along with everything else the package keeps there. Worth doing once, deliberately, early in a project rather than late.

## Related pages

* [Proxy Bindings](/binding-system-3/overview/proxy-bindings.md): the storage rule, and what is bindable.
* [Bind Variables](/binding-system-3/overview/bind-variables.md): scene containers versus project assets.
* [Sources](/binding-system-3/overview/sources.md): the modes an asset binding can actually use.
* [Settings](/binding-system-3/reference/settings.md): the Bindings Path, and the visualization toggles.
* [TextMeshPro and Unity UI](/binding-system-3/reference/integrations/unity-ui.md): material properties, including Tiling and Offset.
