Extend the package
Create a custom persistence manager
A custom manager saves to storage none of the built-in ones reach, such as a console's save container or
a service of your own. To write one, subclass
PersistenceManager and implement three
methods:
protected override LoadResult ReadPayloadSync (PersistenceSource source, string slot);
protected override SaveResult WritePayloadSync(PersistenceSource source, string slot, byte[] payload);
protected override ClearResult ClearPayloadSync(PersistenceSource source, string slot);
Each one moves a byte[] payload, and none of them may touch Target. Override
the async trio instead to run the work off the main thread.
The result you return decides what the package does next:
| Result | What it means |
|---|---|
Invalid | There is no save, which is the normal first run. The next save overwrites. |
Corrupt | A save is there and wouldn't decode. The next save overwrites. |
Failure | Storage was unreachable. The target stays not ready, so nothing can overwrite it. |
The base settings are virtual properties with no backing field. Override one with a
[field: SerializeField] auto-property to expose it in the Inspector window, or with a plain
getter to fix its value. Mark the fields that change where or how a save is written
[Breaking(...)], so the Inspector window can
lock them after release.
The following members cover what a manager can add beyond those three methods.
| To do this | Use |
|---|---|
| Name it in the dropdown | [InspectorDisplay("My Backend", "Saves to my service.", Order = 100, Category = "My Studio")]. |
| Draw a custom Inspector | Inherit PersistenceManagerEditor and override the Draw hook you want, calling base to keep the standard fields. |
| Join Delete Local Data | A static editor method marked [ClearLocalData(typeof(MyManager))] joins Delete Local Data. |
| Write a test manager | [PersistenceManagerInfo(StorageKind = StorageKind.Test)]. It never ships: a release build fails while one exists. Storage that keeps nothing takes StorageKind.Transient. |
| Write to its log | Log("Retrying upload", LogLevel.Warning) adds a line to the manager's log in its Inspector window and in the debug overlay, not to the console. It is thread safe, keeps the last 500 lines, and is stripped outside the Editor and development builds. |
| Forward to other managers | On, ComposedManager and DisabledReason, the way Platform (Routing) does. |
Create a custom serializer
To save data in a format none of the built-in serializers write, subclass
Serializer, mark
it [Serializable], and implement SerializeToString and
DeserializeFromString. For a binary format, override IsBinary and the two byte
methods instead. Your class then appears in the Serializer dropdown.
Two more overrides cover what the package does around a payload:
- For save upgrades, override
ParseToNodeand setSupportsNodeto true. - For a field that holds a project asset, write
AssetRegistry.IdOf(asset)and read it back withAssetRegistry.TryResolve.
To draw the serializer's own fields, inherit
SerializerDrawer.
Create a custom remote manager
RemotePersistenceManager
adds an offline cache, a push loop and conflict handling to
a manager, so a remote service of your own gets all three. Implement
FetchRemote, StoreRemote and DeleteRemote, and map your service's
responses onto an Answer:
| Answer | Map it from |
|---|---|
Success | The service answered, and handed back what you asked it for. |
NoSave | The service answered, and holds nothing for this player. |
Unreachable | The network or the service is down, and a retry can recover. |
SetupError | A permanent fault, such as no URL, which retrying can't fix. |
Add your own settings
Subclass SettingsEntry, and its public fields
become a section of Project Settings > Persistent Asset.
Settings.Get<T>() reads them back, in a player as
well as in the Editor. For a layout of your own, inherit
SettingsEntryDrawer.
Add a component codec
To save a component Scene Objects doesn't cover, or to replace one it
ships, inherit SceneComponentCodec.
Restoring runs in four phases: switch off anything that would overwrite the value, write it, switch that back on, then, once the load has switched every object on or off, start what has to run live, such as a video resuming.
Add your own variable types
A codec is what turns your own type into a No-Code variable. A converter and a widget are optional on top of it.
| Extension point | Description |
|---|---|
| A variable type | A VariableTypeCodec<T> whose Write and Read turn your type into a string and back, registered with [VariableType]. |
| A value converter | The package finds a ValueConverter<TFrom, TTo> subclass automatically, which lets a Binder drive a member of another type, one way per converter. |
| An Inspector widget | A VariableWidget<T> paired with a codec id through [VariableWidget] draws the value in the Inspector window. |
A codec of your own has three rules to follow. It formats and parses with
CultureInfo.InvariantCulture, it treats an empty string as the default value, and it never
throws on corrupt input.