玩家对象
一个属于每位玩家、并且跟着他们一起保存的对象,以及脚本如何找到正确的那份副本。
Save 是一抽屉贴了标签的值。玩家对象则是 VRChat 发给每位玩家、并为他们记住的一整个 GameObject。
它们适合的东西不同。背包、随身携带的工具、设置面板:任何有结构的、大的、经常变的东西。它还有自己单独的配额,不会和你保存的值抢地方。
建立一个
把对象放进场景,加上 VRCPlayerObject 组件。再加上 VRCEnablePersistence,它的同步变量就会在两次到访之间被记住。
接着 VRChat 会做一件常让人绊倒的事:你的对象变成了模板。 它被关掉并留在原地,而每位玩家拿到属于自己的一份副本。真正在跑的是副本。副本在有人到来之前并不存在,所以场景里的任何东西都不能指向一份副本。
找到正确的那份
因此脚本无法持有它想要的实例的引用。它持有的是模板——那也是唯一能拖进检视面板的东西——然后去问哪一份副本属于谁:
public Inventory template; // 把模板拖进来
public override void OnSaveReady()
{
Inventory mine = PlayerObjects.Mine(template);
mine.Add("hat");
}
要拿别人的:
Inventory theirs = PlayerObjects.Of(player, template);
不带参数的 PlayerObjects.Mine() 会把属于这个客户端玩家的所有对象都交给你,如果你更想自己翻找的话。
所有权
玩家拥有自己的副本,而且不能转移。这条规则挡住了有人把别人的东西拿走,同时也意味着你在修改一个玩家对象之前,从不需要去请求所有权。
同一条时机规则
玩家对象读入自己保存值的时刻,和保存值到达的时刻是同一个。所以这里读取的地方也是 OnSaveReady。在 Start 里读,拿到的是数据落地之前这个对象的样子。
空间
保存值大约每位玩家 100 KB,玩家对象再来 100 KB。超出并不会让世界停下:VRChat 记一条错误然后停止保存,于是它继续运行,只是悄悄不再记住任何东西。
public override void OnPersistenceUsageUpdated()
{
int used = Storage.SaveUsed();
int limit = Storage.SaveLimit;
}
这些数字在这里读,别处不要读。 它们不是一直维护着的,而是偶尔才重新算一次,在第一次计算之前读到的是零——那意思是"还没数",不是"没地方了"。override OnPlayerDataStorageWarning,就能在还来得及做点什么的时候,知道某位玩家正在逼近上限。