No-Code For designers
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/4andVector2Int/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.
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.
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.
"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.
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.
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).
Slots copy / rename / delete operations are all code-only. Inspector-driven,
no-code slots are on the Roadmap.
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.
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.
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
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);
}