Variables#
Both Local Variables and Global Variables implement Variable. Code written against the interface can read, write, reorder, and observe either container.
The interface#
The interface also implements the subscription contract used by change bindings. Its declaration, with the members omitted, is:
public interface Variable : ISubscriptionVariable exposes list operations and change notifications without requiring the concrete component or asset type. These members read and modify the container:
| Member | Does |
|---|---|
Count |
The number of entries. |
IsEmpty |
Whether there are none. |
IsLocal |
Whether this is a component rather than an asset. |
Get<T>() |
Reads the first entry. |
Get<T>(int index) |
Reads an entry by position. |
Set<T>(T value) |
Writes the first entry. |
Set<T>(int index, T value) |
Writes an entry by position. |
TagToIndex(IdString tag) |
Resolves a tag to a position, or -1. |
GetTag(int index) / SetTag(…) |
Reads and writes an entry's tag. |
Insert<T> / Replace<T> / Remove |
Modify the list. |
Move / Swap |
Reorder it. |
Contains<T> |
Tests for a value. |
Clear() |
Empties it. |
Export() / Import(…) |
Round-trip the whole list. |
This sequence finds the health entry, reads it as a double, and writes the reduced value:
LocalVariable variables = target.GetComponent<LocalVariable>();
int index = variables.TagToIndex(new IdString("health"));
double health = variables.Get<double>(index);
variables.Set(index, health - 10d);TagToIndex returns the position used by both Get and Set.
Out-of-range indices behave differently per method
Get<T> returns default, and Replace / Remove / Move / Swap do nothing. However, Set<T>(index, value) appends a new entry when the index is out of range. Check the index before writing, because a TagToIndex result of -1 would otherwise grow the list.
Insert<T> creates an entry with an empty tag. Follow it with SetTag when the new entry needs an identifier.
Entries aren't fixed to a type. When T differs from the stored type, Set<T> replaces the underlying value object instead of converting it. Writing a string into a Number entry therefore turns it into a String entry.
Tags are IdStrings#
Entry tags are IdString, a struct that wraps a hash-based PropertyName. Declare repeated tags once:
private static readonly IdString HEALTH = new IdString("health");HEALTH can be reused without reconstructing and hashing the tag for each call.
The constructor replaces any character that isn't a letter, digit, -, or _ with -. For example, new IdString("player health") becomes player-health. Comparison is case-sensitive.
Don't construct an IdString per call
Constructing an IdString allocates memory and hashes its value. Declare repeated tags as static readonly fields to avoid that work on every update.
Numbers are doubles#
Number entries store double. Reading one as float converts the stored value, while reading double uses the underlying type directly:
double exact = variables.Get<double>(index);
float approximate = variables.Get<float>(index);Use double in repeated reads unless the consuming API requires float.
Finding by tag#
VariablesManager maps each tag to the containers holding it. Query one or every registered container with the same IdString:
Variable variable = VariablesManager.Get(HEALTH);
IReadOnlyList<Variable> all = VariablesManager.GetList(HEALTH);A container is listed once per entry carrying the tag. Empty tags are never registered.
Both methods return containers. Call TagToIndex on a returned container to find the matching entry's position.
Get returns the first match, but that order isn't stable when several containers share a tag. Use GetList for multiple matches or resolve a known component directly.
Registration follows the component's enabled state, so queries don't find a disabled Local Variables component. Insert, Remove, Clear, Import, and SetTag also update the registry as the list changes.
Subscribing to changes#
Variable implements ISubscription for change notifications. Keep the returned Guid so the component can unsubscribe:
private Guid m_Subscription;
private void OnEnable()
{
using Args args = Args.Get(this.gameObject, this.gameObject);
this.m_Subscription = this.m_Variables.Subscribe(this.OnChanged, args);
}
private void OnDisable()
{
this.m_Variables.Unsubscribe(this.m_Subscription);
}
private void OnChanged(Args args)
{
// …
}The Args passed to Subscribe is copied internally, so the one you pass can be disposed immediately. The callback receives that copy.
Every Subscribe needs its Unsubscribe
Keep the returned Guid and release it when your object goes away. A leaked subscription fires into a destroyed component.
Export and import#
Export() returns the entries as an array; Import(entries) replaces the contents.
You can use them to copy state between containers, capture a snapshot for an undo system, or build a container procedurally. The save system uses the same operations.
Global variables#
GlobalVariable is a ScriptableObject implementing both Variable and ISaveLoad.
At startup, VariablesManager reads VariablesRepository. For each asset, it calls Initialize(), registers its tags, and subscribes it to SaveLoadManager, which makes the asset participate in saving.
The editor adds assets to the repository on import. A GlobalVariable created at runtime isn't included in that process, so the manager doesn't initialize or register it automatically.
Editing at runtime versus authoring#
Initialize() clones the authored entries into a separate runtime list, and everything at runtime works on that clone. Writing to a GlobalVariable during Play mode never modifies the asset on disk, and values revert when Play mode ends. ISaveLoad.OnReset re-clones from the authored entries.
Where to go next#
- Custom variable types — add a stored value type.
- Variables — inspect the designer-facing containers.
- Save and Load — follow how Global Variables register and persist.