Saving your own components#
ISaveLoad is the interface implemented by every save-system participant. Implement it on your own component or ScriptableObject when the type should save itself without a Remember component.
The interface#
This manager subscribes for its lifetime and saves one integer in a dedicated data class:
using System;
using UnityEngine;
using VisualScript.Runtime;
public class MyManager : MonoBehaviour, ISaveLoad
{
[SerializeField] private SaveOptions m_SaveOptions = new SaveOptions();
[SerializeField] private int m_Score;
[Serializable]
public class Data
{
[SerializeField] public int score;
}
private void Awake() => SaveLoadManager.Subscribe(this);
private void OnDestroy() => SaveLoadManager.Unsubscribe(this);
IdString ISaveLoad.Id => this.m_SaveOptions.Id;
Type ISaveLoad.Type => typeof(Data);
bool ISaveLoad.IsShared => false;
bool ISaveLoad.IsPersistent => true;
void ISaveLoad.OnReset() => this.m_Score = 0;
object ISaveLoad.OnSave() => new Data { score = this.m_Score };
Awaitable ISaveLoad.OnLoad(object value)
{
if (value is Data data) this.m_Score = data.score;
return AwaitableUtils.DontAwait;
}
}SaveOptions supplies the stable ID, while Data defines the serialized contract. Awake and OnDestroy keep the manager registered only while it exists.
The properties#
These properties define which saved data belongs to the object and when the manager restores it:
| Property | Means |
|---|---|
Id |
Unique identity. Two objects with the same ID are treated as the same object. |
Type |
The type of the data returned by OnSave. |
Priority |
Restore order, higher first. Defaults to 0. |
CanSave |
Whether to write at all. Defaults to true. |
IsShared |
true for data common to every profile, false for per-profile data. |
IsPersistent |
true if the object survives scene loads, false if it's scene-bound. |
IsPersistent controls restore timing#
Choose IsPersistent based on whether your object survives the scene load:
| Value | Restored | Suits |
|---|---|---|
true |
Before scenes load | Managers, singletons, assets |
false |
As its scene loads | Components on scene objects |
Marking a scene-bound object as persistent restores it before the scene load. The load then destroys the object and silently discards the restored state.
Id must be stable#
The manager uses the ID to match stored data to the object that receives it.
Never change an ID after shipping
Existing saves reference the old ID. Changing it makes the data unreachable, so the object returns with default state and no migration error.
The built-in SaveOptions field provides the same identity UI as the Remember component, including designer-set and generated IDs. Prefer it to a separate ID implementation.
Subscribing#
Subscribe when the object initializes and unsubscribe when its lifetime ends:
| Kind | Subscribe in | Unsubscribe in |
|---|---|---|
MonoBehaviour |
Awake |
OnDestroy |
ScriptableObject |
Initialization | Never — assets outlive the session |
Forgetting to unsubscribe keeps a destroyed component in the subscriber list. The next save calls OnSave on that component.
OnSave and OnLoad#
OnSave returns any serializable object. Use a dedicated, flat data class to make the stored format explicit and keep component changes separate from save compatibility.
OnLoad receives it back as object. Always type-check rather than casting, because a save written by an older version may hold something different:
if (value is not Data data) return AwaitableUtils.DontAwait;The type check returns immediately when the loaded value isn't Data. When the value is valid, OnLoad can await any required initialization before restoring it.
OnReset#
The save system calls OnReset when the game returns to a new-game state. Restore the component's authored defaults there.
Without this reset, state from the previous profile leaks into the new game.
Versioning#
Players may load saves written by an earlier version of your game. Two habits help preserve compatibility:
- Add fields; do not repurpose them. A new field receives its default in old saves, while a changed meaning corrupts the interpretation of existing data.
- Include a version number in the data class from the first release. It provides the branch needed for a later migration.
Where to go next#
- Custom memories — capture components you do not own.
- Save and Load — call
SaveLoadManagerand observe its events. - Save & Load — inspect the designer-facing lifecycle.