The No-Code modules let you store values and react to them entirely in the editor, with assets, components and UnityEvents. (Programmers can reach everything from code too; that is the last section.)

Persistent Variables

PersistentVariables is a ready-made asset that holds a named set of values you author in the inspector. It is a persistent object like any other, so it gets a Persistence Manager dropdown and saves the same way, with no scripting on your part.

Create one via Create > Persistent Asset > Persistent Variables, assign a Persistence Manager, then add variables. Each has a name, a type and a default value. Over 40 types are supported out of the box:

  • Primitives: bool, string, char, and every number type (int, long, float, double, byte, sbyte, short, ushort, uint, ulong, decimal).
  • Any enum (no setup needed).
  • Unity types: Vector2/3/4 and Vector2Int/3Int, Quaternion, Color, Color32, Gradient, Rect, RectInt, RectOffset, Bounds, BoundsInt, Pose, Plane, Matrix4x4, LayerMask, RenderingLayerMask, RangeInt, Resolution, Hash128, Keyframe, AnimationCurve.
  • System types: DateTime, DateTimeOffset, TimeSpan, Guid, Version, Uri.
  • Lists and maps of any of the above.
Persistent Variables inspector
Author variables, names and defaults in the inspector
Renaming a variable is safe at any time, even after release: components stay wired to it and existing saves still load into it.
Handy for programmers too: a PersistentVariables asset is a ready-made dynamic data bag, a named, typed, observable set of values that saves itself, with no data class to write. Reach it from code through a VariableRef (see For developers).

The components

Three components connect a variable to your scene, each configured in the inspector:

  • Variable Binder: keeps a variable and a target in sync, one-way or both ways as you choose, so a change on one side updates the other (a UI slider and a "volume" variable, for example).
  • Variable Listener: raises a UnityEvent whenever the variable changes, so you can trigger anything from the inspector when it does.
  • Variable Setter: changes a variable from a UnityEvent (a button click). It can Set, Toggle, Increment, Add or Reset the value.
Variable components in the inspector
Bind, listen and set, all from the inspector

A typical flow: a Setter on a button increments a "coins" variable, a Listener updates a label when it changes, and the whole thing persists because the PersistentVariables asset has a manager. No script anywhere.

When a Binder drives a text label, give it a Format string to turn the value into readable text: a composite like "Score: {0}" or a numeric specifier like "N0". Formatting a value into text is one-way (variable to label).

Connecting variables (drag and drop)

There are two ways to point a component at a variable, both drag-and-drop friendly. To drag one, grab it by its hand, the drag handle at the left of the variable's row in the Persistent Variables inspector.

Onto a component field

Every component has a variable field. Fill it by dragging a variable from the Persistent Variables asset onto the field, or by assigning the asset and picking the variable from the dropdown next to it. Either way takes seconds and needs no code.

Assigning a variable to a field
Drag a variable in, or choose it from the dropdown

Onto a GameObject

Even quicker: drag a variable straight onto a GameObject (in the Hierarchy or the Scene). Persistent Asset asks what to create, a Binder, a Listener or a Setter, then prompts for whatever that component needs (a binder's target property, for instance) and adds it already wired, so a single drag can connect a variable to a slider or a label end to end.

Dragging a variable onto a GameObject
Drop a variable on a GameObject and pick what to create

Saving and loading from the inspector

Persistence is automatic, so usually you wire nothing. When you do want an explicit save, load or clear from a button, the manager offers Save, Load and Clear actions you can pick straight from a button's event list, no script needed. These zero-argument actions are fire-and-forget, so they work even for an asynchronous manager (a server or Cloud Save).

Anything slot-related still needs a small script today: switching the active slot, building a save-select menu (a slot registry), and the Slots copy / rename / delete operations are all code-only. Inspector-driven, no-code slots are on the Roadmap.
Wiring Save to a button OnClick
Drop Save / Load / Clear straight onto a UnityEvent

Input bindings

The Input module makes a player's remapped controls persist between sessions. It adds an Input Binding variable type, so a control binding can be stored like any other persistent value and is restored on the next launch.

Requires the Unity Input System package in your project.

Wiring a "press a key to rebind" screen needs a few lines of glue (capturing the new binding and applying the saved one on startup); that small API is in For developers.

Localization

The Localization module persists the player's chosen language. It adds a Locale Identifier variable type, so the selected locale is stored like any other value and reapplied on the next launch.

Requires the Unity Localization package in your project.

Applying the saved locale on startup and switching it from a language dropdown use a small helper, covered in For developers.

For developers

Everything above is reachable from code too, for when you want to mix scripting with the no-code setup.

Reading and writing variables

Reference a variable with a non-generic VariableRef field when the type is not known ahead of time, or a typed VariableValueRef<T>, VariableListRef<T> or VariableMapRef<TKey, TValue> when it is. Either is assigned in the inspector the same way as a component (drag or dropdown), then you read and write its live value in code. Collection variables surface as ObservableList<T> and ObservableDictionary<TKey, TValue>, which raise change events.

public class Shop : MonoBehaviour
{
    [SerializeField] VariableValueRef<int> coins;            // assigned in the inspector
    [SerializeField] VariableListRef<string> unlockedSkins;
    [SerializeField] VariableMapRef<string, int> owned;      // a map variable

    public void Buy(int price)
    {
        if (coins.Value >= price)
            coins.Value -= price;                       // read and write the live value

        unlockedSkins.List.Add("gold");                  // ObservableList<T>, raises change events
        owned.Map["gold"] = 1;                          // ObservableDictionary<TKey, TValue>, also observable
    }
}

Type conversion on binders

A VariableBinder can route through a ValueConverter to adapt the variable's type to the bound member's type (an int variable driving a string label, say). Built-in converters cover the common cases; add your own by subclassing ValueConverter<TFrom, TTo> (it is discovered automatically), and register one each way for a two-way binding:

public class IntToString : ValueConverter<int, string>
{
    protected override string Convert(int value) => value.ToString();
}

Input & localization helpers

The Input and Localization modules each expose a static helper for the bits that need code:

  • InputBindingPersistence: apply a stored binding on startup, capture the current one, or rebind and store in a single call for a remap screen.
  • LocalePersistence: apply the saved locale on startup, capture the active one, or select a locale and store it for a language dropdown.
// Both are extension methods on your PersistentVariables asset
variables.ApplyBinding("Jump", jumpAction);                  // restore on startup
variables.RebindAndStore("Jump", jumpAction);                // run a remap screen, then store

variables.ApplyLocale("Language");                          // restore on startup
variables.SelectLocale("Language", new("fr"));               // pick one from a dropdown
Two caveats for these helpers. Call ApplyLocale / SelectLocale only after Unity Localization has finished initializing (await LocalizationSettings.InitializationOperation); before that they do nothing and return false. And RebindAndStore returns a RebindingOperation you should cancel in OnDisable / OnDestroy, so its completion callback never runs on a destroyed object.

Custom types and converters

You can add your own persistable variable types (a VariableTypeCodec<T> with [VariableType(typeof(T), "id")]), your own type conversions (a ValueConverter), and your own inspector widgets, exactly how Input Binding and Locale Identifier are built. See Extending.

// Make a custom type persistable: write it to a string and read it back
[VariableType(typeof(Guid), "guid")]
public class GuidCodec : VariableTypeCodec<Guid>
{
    protected override string Write(Guid value) => value.ToString();
    protected override Guid Read(string text) => Guid.Parse(text);
}