Saves
Brain keeps each player's saves with their account, so their progress follows them to every device and sign-in. A player who reinstalls and signs in with Game Center, Play Games, Steam, Apple or Google gets their progress back, and a guest who links an account keeps theirs.
Turn saves on in brain/game.toml:
[saves]
How saves work
- A save is a JSON object in a named slot. A game with one save uses the slot
main; a game with save files names its own (1 to 16 characters ofa-z,0-9and_). - At most 64 KiB per save, plus a summary of at most 1 KiB that a file-select screen shows.
- The device first. A save writes the device at once and the account a few seconds later. A burst of saves goes as one. Offline or signed out, the game plays on and the save goes once it can.
- Your format's version is
schema, a number you raise whenever the save's shape changes. Brain never reads your data, but it refuses a write with an older schema than the stored save's, so an old build can't overwrite what a newer one wrote. - Nothing is overwritten silently. When two devices save the same slot from the same starting
point, they conflict. The save with more
progresswins, or your game merges the two itself. - Seasons follow your game's stage. Saves made while the game is in playtest don't carry over to launch: players start fresh.
Keep purchases out of saves. A save is the player's own claim, so what they bought comes from Brain
(Purchases). The exception is a deliver product, which hands an amount to your
save once.
Platform cloud saves
Brain is your game's cloud save. Don't also sync progress through Steam Cloud, iCloud or Play Games Saved Games: two clouds fight over one save, and those services join no account linking, export or deletion.
Set up, load and save
Unity has no Brain.Saves facade yet. The save commands are available one level down, on
Brain.Services.Saves, and their results arrive as JSON on Brain.Output:
using System;
using Ase.Brain;
[Serializable] public class GameSave { public int level; public int xp; }
[Serializable] public class SaveSummary { public int level; }
GameSave save = new GameSave();
bool setUp;
void Start()
{
Brain.Output += OnBrainOutput;
}
void Update()
{
if (!Brain.Ready || setUp) return;
// Ok means Brain took the command; Full and NotReady mean try again next frame.
if (Brain.Services.Saves.Setup(3, false) != BrainStatus.Ok) return;
Brain.Services.Saves.Load("main");
setUp = true;
}
void SaveGame()
{
Brain.Services.Saves.Put("main", save, new SaveSummary { level = save.level }, save.xp);
}
void OnBrainOutput(string json)
{
// Load and List answers, and the Save events below, arrive here as JSON text.
}
Put takes the slot, the data, the summary and the save's progress. Data and summary are objects
JsonUtility serializes, so its rules apply: [Serializable] classes and fields, no dictionaries.
| Command | Does |
|---|---|
Setup(schema, merges) |
Your format's version, and whether your game merges conflicts itself |
Load(slot) |
Reads a slot; the answer arrives on Brain.Output |
Put(slot, data, summary, progress) |
Saves a slot |
Erase(slot) |
Empties a slot |
List() |
Lists the slots that hold a save |
Flush() |
Sends waiting saves now, when the app pauses or quits |
Migrated(slot, data) |
Hands back a save you migrated to the current schema |
Merged(slot, data, summary, progress, baseRevision, mergedAt) |
Hands back your merge of two saves |
MergeFailed(slot) |
Your game couldn't merge; the device keeps its copy |
Save events on Brain.Output carry a Save object holding one of Changed, Merge, TooNew or
Refused. BrainSaveDocument<T> reads a loaded {schema, data} document into your own type.
Migrations
Add one step per version, from each old version to the next. Steps run in order on every older save, so never delete one: a player may come back after many versions. A missing step is an error, and the save stays as it is.
Migrate the loaded data in your own code, then hand it back with
Brain.Services.Saves.Migrated(slot, data).
Conflicts and merges
Two devices that saved the same slot from the same revision conflict. Without a merge, the save
with more progress wins, and on a tie the newer one. Give a progress whenever saves can
conflict, and merge yourself when losing either side's progress would hurt: take the max of each
counter, the union of unlocks.
When two accounts merge (a guest links an account that already has saves), your merge also folds in the save the other account had.
Call Setup(schema, true). A Merge save event then hands you both saves; answer with
Merged(...), or MergeFailed(slot) when you can't.
Saves that change under you
Another device's newer save, a merge, or an emptied slot (erased elsewhere, a new season, the account deleted) replaces your game's copy. Use the new one:
- Unity: the
Changedsave event onBrain.Output.
When the player's save is from a newer version of your game, ask them to update. Nothing of that save is sent or overwritten until then:
- Unity: the
TooNewsave event.