Skip to content
One Last Click Docs

How It Works

Unity does not natively serialize interface fields. Unity Object Reference works around this limitation using two main methods:

  • Serializable UnityObjectReference<T> type, which stores a UnityEngine.Object reference that Unity can serialize
    • A custom drawerer is then used so you can set this in the inspector with validation the object implements the interface.
  • ILPostProcessing to transform supported fields after compilation, so you dont need to manually change all your fields to use UnityObjectReference<T>

This lets you write normal C# interfaces in your source code while the generated assembly uses a serializable UnityObjectReference<T> behind the scenes.

You can declare an interface field normally:

[SerializeField]
private IPlayerCueInputProvider _inputProvider;

From your source code’s perspective, this is a regular interface field. You can access it directly:

_inputProvider.DoSomething();

During the ILPostProcessing step, the field is transformed into a UnityObjectReference<T>.

Conceptually, this:

[SerializeField]
private IPlayerCueInputProvider _inputProvider;

becomes:

[SerializeField]
private UnityObjectReference<IPlayerCueInputProvider> _inputProvider;

The transformation happens in the compiled assembly, so your source code does not need to explicitly use the wrapper.

Field accesses are transformed as well.

An access such as:

_inputProvider.DoSomething();

is rewritten to access the wrapped value:

_inputProvider.Value.DoSomething();

This means the generated code interacts with the actual interface implementation while Unity serializes the UnityObjectReference<T>.

UnityObjectReference<T> stores a reference to a UnityEngine.Object rather than attempting to serialize the interface itself.

When .Value is accessed, the stored Unity object is cast to the requested type:

public T Value => _object as T;

This provides the bridge between Unity’s serialization system and normal C# interfaces.

The wrapper can therefore serialize objects such as:

  • MonoBehaviour components.
  • ScriptableObject assets.
  • Other UnityEngine.Object instances compatible with T.

A custom property drawer provides the Inspector experience for UnityObjectReference<T>.

It supports dragging compatible Unity objects directly onto the field.

For example, if the field is:

[SerializeField]
private IPlayerCueInputProvider _inputProvider;

you can drag a component implementing IPlayerCueInputProvider onto it.

You can also drag a GameObject onto the field.

When this happens, the drawer searches the GameObject for a component matching the required type and uses the first matching component it finds.

This allows the Inspector to work naturally with both components and their containing GameObjects.

The drawer also validates assigned objects against the requested type.

If an object cannot provide the required interface, the Inspector displays a warning rather than silently accepting an invalid reference.