Getting started
Save a value, read it back with a fallback, and do the reading at the only moment the answer is real.
Save something
Save.Set("coins", 25);
Save.Set("name", "ada");
Save.Set("spawn", transform.position);
One verb for every type. The value picks which one is used, so there is no SetInt to get wrong.
Writing always applies to the player at this client, because that is the only data a client is
allowed to change.
Read it back
int coins = Save.Get("coins", 100);
string name = Save.Get("name", "friend");
Vector3 spawn = Save.Get("spawn", Vector3.zero);
The second argument is what you get when nothing is saved, and it is required. That is the
whole point: a returning player gets their 25 coins, a new one gets 100, and the code says which
is which without a TryGet and an if.
It is also what you get when the key holds a different type than you asked for, so two systems
that both picked the name "level" degrade to a default rather than to nonsense.
Read at the right moment
public class Shop : SaveBehaviour
{
private int coins;
public override void OnSaveReady()
{
coins = Save.Get("coins", 100);
}
}
Do not read in Start. Saved data arrives shortly after the player joins, and anything read
before then is the fallback. This is the mistake worth spending a paragraph on, because it does
not look like one: in the Editor there is often nothing saved anyway, so the world behaves
exactly as intended right up until a real player with real progress walks in.
Deriving from SaveBehaviour gives you OnSaveReady, which runs once, for the player at this
client, at the moment their data is readable.
Somebody else's data
public override void OnPlayerSaveReady(VRCPlayerApi player)
{
int theirs = Save.Get(player, "best", 0);
}
You can read anybody's saved values, and their data becomes readable at a different moment from your own — when they arrive, which may be an hour later. That is why it is a separate method: a behaviour that reloaded its own state every time a stranger joined would throw away everything done since.
Telling a first visit from a returning one
if (!Save.Has("visits"))
{
// Hand out the welcome pack.
}
Save.Has answers whether a key has ever been written. Most of the time a fallback says the same
thing more directly; reach for this when the absence itself is the information.
Two limits worth knowing
Nothing can delete a key. VRChat can overwrite one but not remove it, so the set of keys a world uses is worth deciding once rather than letting it grow.
Keys are shared across the whole world. Two systems that both save "level" are using the
same key. Give them prefixes.