Bound slots: SaveData.Bind
Bind is the shortest way to keep data in the player’s save. You get a slot whose Value is saved and loaded for you.
SaveData make a slot that is saved and loaded for you
Bind<T>(modId, key) is the only method here. It gives you a SaveSlot<T> that the framework saves and loads for you.
public static class SaveData
{
public static SaveSlot<T> Bind<T>(string modId, string key) where T : class, new();
}
| Method | Returns | What it does |
|---|---|---|
Bind<T>(modId, key) | SaveSlot<T> | Makes a slot that is saved and loaded for you. |
SaveSlot<T> your saved data
Bind gives you one of these. Keep your data in Value, and read Value again after a load, because a load replaces the object.
public sealed class SaveSlot<T> where T : class, new()
{
public T Value { get; set; }
}
| Property | Type | What it means |
|---|---|---|
Value | T | Your data. Never null. Setting it to null throws ArgumentNullException. |
Register and Bind side by side
Here is the same data kept with Register and with Bind:
MyState state = new MyState();
SaveData.Register<MyState>("com.example.mymod.state", () => state, s => state = s ?? new MyState());
state.Coins++;
SaveSlot<MyState> slot = SaveData.Bind<MyState>("com.example.mymod", "state");
slot.Value.Coins++;
Things to know
Bindsaves your data in a file named after your mod id and the key. The file is<Folder>\mods\<modId>.<key>.json.modIdandkeycan use letters, digits,.,_and-, and the joined name can be at most 64 characters.CoreMod.Save(key, ...)for the same mod uses the same file. (it registers the name<modId>.<key>throughSaveData.Register.)Valueis a newTuntil a load, and a load replaces it. The load gives you the saved object, or a newTwhen the file is missing or cannot be read. ReadValueeach time instead of keeping a reference to the old object.- Set
Valueafter the load has finished. If a save is already loaded whenBindruns, the value loads at once. Otherwise it loads onEvents.Loaded, which replaces any value you set before it. Set your changes insideEvents.WhenReady, or after it, so the load does not undo them. - Your data is saved on the game’s normal saves, and sometimes the framework holds a save back to protect it. A level change (going through a door) saves your data even though a save is “busy”.
- Going through a door reloads your data from the autosave, so it brings back what was just saved.
Limits
- Using the same name twice throws
InvalidOperationException. A secondBindorSavewith the same name does, just asRegisterdoes. - A save made while a load from the menu or the pause menu is still running writes no
RegisterorBindvalues, because they still hold the previous save’s data. A load that fails ends that wait, so saves write again. - A save is also skipped for a file that could not be read at the last load, until a load reads it again. A load is never skipped. (
SaveData.Busydoes not skip a save. A level change saves whileBusyis true. A level change is not skipped this way unless it starts while such a load is still running.) - A change you make between the save and the load of that door change is lost. For example, a change made in an
Events.SceneLoadedhandler during a door change is overwritten by the saved value. Make such changes inEvents.LoadedorEvents.WhenReady, or ignore that window. A save that fails keeps the previous file, so the reload brings back the previous value. (a door change (SaveController.ChangeLevel) runsSaveGamebefore it loads the new level.SaveGamecopies the loaded save’s files into the older autosave slot (AutosaveorAutosave2) and raisesSavingDone, which writes everyRegisterandBindvalue to that slot’smods\<modId>.json. The level load then reads those files back. A failed save is an exception in a save callback or a file over 64 MB.)