SaveSystem and Settings
Two systems with different jobs and different durability guarantees. Settings holds player preferences; SaveSystem holds progress, with atomic writes, a backup and a checksum. See the Saving and settings guide for the concepts.
Settings
A flat key/value store backed by a file.
| Method | Notes |
|---|---|
Settings.GetFloat(string key, float defaultValue = 0f) | |
Settings.GetInt(string key, int defaultValue = 0) | |
Settings.GetBool(string key, bool defaultValue = false) | |
Settings.GetString(string key, string defaultValue = "") | |
Settings.SetFloat(string key, float value) | |
Settings.SetInt(string key, int value) | |
Settings.SetBool(string key, bool value) | |
Settings.SetString(string key, string value) | |
Settings.Has(string key) | Whether the key exists |
Settings.Erase(string key) | Removes one key |
Settings.Clear() | Removes everything |
Settings.Save() | Writes to disk. Returns true on success |
Settings.Load() | Reads from disk. Returns true on success |
float volume = Settings.GetFloat("audio.master", 1.0f);
Settings.SetFloat("audio.master", 0.8f);
Settings.SetBool("gameplay.screenShake", false);
Settings.Save();Every getter takes a default, so a fresh install with no settings file behaves exactly like one saved with defaults. Call Save() when the player leaves the options menu, not on every slider tick.
Reserved key prefixes
The engine reads these itself — name your own keys outside them.
| Prefix | Used for |
|---|---|
audio.* | Master volume and other audio settings |
display.* | Resolution, fullscreen |
input.* | Key rebind overrides |
language | The player's chosen language |
haptics.* | Vibration intensity |
SaveSystem
SaveSystem.Save(int slotIndex)
Writes the current state to a slot. Returns true on success.
SaveSystem.Load(int slotIndex)
Reads a slot back. Returns true on success. Falls back to the .bak copy if the primary file is corrupt.
SaveSystem.Delete(int slotIndex)
Deletes a slot, primary and backup. Returns true on success.
SaveSystem.HasSave(int slotIndex)
Whether a slot has a save, without loading or deserializing it.
SaveSystem.EnumerateSlots()
The indices of every slot that has a save, in order.
if (SaveSystem.Save(0))
ShowToast("Saved");
else
ShowToast("Couldn't save");Check the return value
A full disk, a revoked permission, and a browser blocking storage all show up here. A save screen that silently claims success is worse than no save screen.
Choosing what gets saved
Nothing is saved automatically — you register a pair of callbacks under a name, and the save system calls them at the right moment.
SaveSystem.RegisterSaver(string name, Action<SaveWriter> fn)
SaveSystem.RegisterLoader(string name, Action<SaveReader> fn)
SaveSystem.ClearRegistrations()
Removes every registered saver, loader and migration.
public class Player : GameEntity
{
private float m_Health = 100f;
private int m_Coins;
protected override void OnCreate()
{
SaveSystem.RegisterSaver("player", w =>
{
w.Write("hp", m_Health);
w.Write("coins", m_Coins);
w.Write("x", Translation.X);
w.Write("y", Translation.Y);
});
SaveSystem.RegisterLoader("player", r =>
{
m_Health = r.ReadFloat("hp", 100f);
m_Coins = r.ReadInt("coins", 0);
Translation = new Vector3(r.ReadFloat("x", 0), r.ReadFloat("y", 0), 0);
});
}
}The name namespaces the keys, so two systems both writing "hp" don't collide.
ISaveable does nothing in 0.1.0
There is an ISaveable interface with OnSave/OnLoad, and its own documentation presents it as the way to opt in. Nothing discovers it. Implementing it on an entity has no effect whatsoever — SaveSystem.Save() only ever walks the callbacks registered above.
If you like the interface shape, implement it and register its methods:
public class Player : GameEntity, ISaveable
{
protected override void OnCreate()
{
SaveSystem.RegisterSaver("player", OnSave);
SaveSystem.RegisterLoader("player", OnLoad);
}
public void OnSave(SaveWriter w) { /* ... */ }
public void OnLoad(SaveReader r) { /* ... */ }
}SaveWriter
Write(string key, float value), Write(string key, int value), Write(string key, bool value), Write(string key, string value).
SaveReader
ReadFloat(string key, float defaultValue = 0f), ReadInt, ReadBool, ReadString — each with a default.
There is no vector overload
Write a position as its components ("x", "y") as in the example above. Four scalar types is the whole vocabulary; anything richer, you decompose yourself.
Giving every read a sensible default is what makes a save written by an older version of your game still load.
Slot metadata
SaveSystem.GetMetadata(int slotIndex, out SaveMetadata metadata)
Reads a slot's summary without deserializing the whole save. Returns false (leaving metadata at its default) if the slot is missing or unreadable.
foreach (int slot in SaveSystem.EnumerateSlots())
{
if (!SaveSystem.GetMetadata(slot, out SaveMetadata meta))
continue;
AddSlotRow(slot, meta.SlotName, meta.Level, meta.PlaytimeSeconds);
}SaveMetadata
| Field | What it is |
|---|---|
SlotName | "Slot 1", "Autosave", whatever you set |
Level | The level or scene name at the save point |
PlaytimeSeconds | Total playtime |
Timestamp | Unix time when it was written |
ScreenshotPath | Optional thumbnail |
IsComplete | A completion flag your game defines |
This is exactly what a load menu needs — reading it per slot costs nothing, where Load() would deserialize every save just to show a timestamp.
Versioning
SaveSystem.SaveVersion
Your save format's version number. Bump it when the format changes.
SaveSystem.GameVersion
Your build's version, recorded alongside each save. Yours to set and interpret.
SaveSystem.RegisterMigration(uint fromVersion, uint toVersion, Action<SaveData> fn)
SaveSystem.SaveVersion = 3;
SaveSystem.RegisterMigration(1, 2, data => { /* reshape v1 into v2 */ });
SaveSystem.RegisterMigration(2, 3, data => { /* reshape v2 into v3 */ });Migrations run before your loaders, so a loader only ever sees the current format. Chains apply in order — a version-1 save moving to version 3 runs 1→2 then 2→3 — regardless of the order you registered them in.
A save that can't be migrated is refused and left on disk, not deleted. A player can send you a file you kept; they can't send you one you destroyed.
Where files go
Under the Company and Game Name from Project Settings, in each platform's per-user location: %APPDATA% on Windows, ~/Library/Application Support on macOS, the XDG directories on Linux.
| File | |
|---|---|
| Settings | settings.yaml (settings.yaml.bad if it fails to parse) |
| Saves | slot0.sav, slot1.sav, … (with slot0.sav.bak) |
On the web there's no filesystem, which is why every one of these calls can return false.
See also
- Saving and settings guide — the concepts and habits
- Session — data that only needs to survive a scene change
- Loc — the
languagesetting - Audio — reading a saved master volume