PlayerData(玩家数据)
PlayerData 是一个键值数据库,用于存储关于玩家的持久数据,例如他们在游戏中的分数或在世界中的偏好设置。
设置
为了使 UdonBehaviour 使用 PlayerData,你只需使用下面描述的各种 PlayerData 函数即可。任何 UdonBehaviour 几乎可以在任何时间、任何位置访问这些函数。
最佳实践
- 使用
OnPlayerDataUpdated时,考虑你的脚本是否可以限制为仅针对本地玩家的更改触发,或者是否也需要为每个远程玩家触发。- 例如 - 视频播放器音量的用户设置可以仅为本地玩家更新,但一个协作撸狗游戏汇总实例的"总撸狗数"分数可以响应任何玩家分数的增加来增加全局分数。
- 在使用 Player Data 之前等待
OnPlayerRestored事件。OnPlayerRestored表示玩家的保存数据已加载完毕,可以安全地通过 Udon 访问。即使你没有保存数据,此事件也会运行。 - 如果你有很多 Player Key,遍历所有 Key 可能会很慢。作为一般准则,如果你只检查几个键或有超过十个键需要检查,请使用
TryGet方法直接检查特定键的值。
网络
PlayerData 不依赖 UdonBehaviour 的同步设置,因为它不绑定到任何特定的 UdonBehaviour。将脚本设置为"None"而不是"Manual"或"Continuous"不会阻止该脚本访问和修改 PlayerData,也不会对 PlayerData 的整体运行产生负面影响。
当 UdonBehaviour 在 PlayerData 中设置值时,PlayerData 会自动同步该值。在内部,PlayerData 以类似于使用 RequestSerialization 事件进行手动同步的方式发送值。这意味着 UdonBehaviour 可以同时设置多个 PlayerData 键并一起发送。UdonBehaviour 不需要单独发送每个键。同样,如果 UdonBehaviour 将一个键设置为一个值然后立即更改为其他值,远程用户不会接收到中间值。如果数据更改过快,远程用户只会接收到最终状态。
PlayerData 的带宽成本与一个设置为"Manual"同步的 UdonBehaviour 类似。
世界中所有 UdonBehaviour 共享对你世界的 PlayerData 的访问。当你设置一个 PlayerData 键时,世界中的任何 UdonBehaviour 都可以访问它。PlayerData 在不同的 UdonBehaviour 之间不是"分离"的。
当你更改本地玩家的任何数据时,会发送所有该玩家的 PlayerData,包括未更改的数据。如果你有少量频繁更新的数据或大量缓慢更新的数据,不太可能遇到Udon 的带宽限制。但如果你使用 PlayerData 同步大量数据和高频数据,应考虑将其中一种数据迁移到玩家对象。你可以使用玩家对象同步持久变量,但每个对象单独同步。这允许你将快速数据与大数据分离,减少世界的网络带宽。
事件
| 事件 | 输出 | 备注 |
|---|---|---|
| OnPlayerDataUpdated | VRCPlayerApi player, PlayerData.Info[] infos | 如果在帧结束时任何玩家的 PlayerData 已更改或收到,则在帧结束时触发。提供与该数据关联的玩家的 VRCPlayerApi,以及该数据中所有键的信息数组。数组中的信息包括用于数据的键以及该数据的状态,例如它是已更改、已添加还是未更改。 |
| OnPlayerRestored | VRCPlayerApi player | 在 VRChat 玩家的持久数据加载完成后触发。 |
| OnPersistenceUsageUpdated | VRCPlayerApi player | 当 VRChat 玩家的持久化使用量更新时触发。 |
| OnPlayerDataStorageExceeded | VRCPlayerApi player | 当 VRChat 玩家的 Player Data 使用量超出允许的存储限制时触发。 |
| OnPlayerDataStorageWarning | VRCPlayerApi player | 当 VRChat 玩家的 Player Data 使用量接近允许的存储限制时触发。 |
在读取或写入 PlayerData 之前,等待 OnPlayerRestored 事件。
当玩家加入时,OnPlayerJoined 事件被调用。然而,可能需要一些额外的时间才能收到他们的 PlayerData。如果你过早设置 PlayerData,当收到持久数据时可能会被覆盖。
PlayerData 信息
OnPlayerDataUpdated 事件提供 PlayerData.Info 数组。此数组包含与该玩家关联的所有当前 PlayerData 键。每个元素包含以下信息:
| 属性 | 类型 | 说明 |
|---|---|---|
| Key | String | 与 PlayerData 键关联的字符串,可用于查询和更改操作。 |
| State | Enum | 在 OnPlayerDataUpdated 发生时 PlayerData 键的最新状态。 |
State 枚举描述这些可能的状态:
| 状态 | 索引 | 说明 |
|---|---|---|
| Unchanged | 0 | 表示自上次更新以来此键中的数据未更改。 |
| Added | 1 | 表示自上次更新以来已添加此键。 |
| Removed | 2 | 表示自上次更新以来此键已被移除。已移除的键将仅在此数组中出现一次,下一次它们将消失*。 注意:目前不支持移除键。 |
| Changed | 3 | 表示自上次更新以来此键中的数据已更改。 |
| Restored | 4 | 表示此键已从持久记录中恢复。这仅在你以前去过某个实例并重新加入时发生。 |
最佳实践
- 使用
OnPlayerDataUpdated时,考虑你的脚本是否可以限制为仅针对本地玩家的更改触发,或者是否也需要为每个远程玩家触发。- 例如 - 视频播放器音量的用户设置可以仅为本地玩家更新,但一个协作撸狗游戏汇总实例的"总撸狗数"分数可以响应任何玩家分数的增加来增加全局分数。
- 在使用 PlayerData 之前等待
OnPlayerDataRestored事件。OnPlayerRestored表示玩家的保存数据已加载完毕,可以安全地通过 Udon 访问。 - 如果你有很多 Player Key,遍历所有 Key 可能会很慢。作为一般准则,如果你只检查几个键或有超过十个键需要检查,请使用
TryGet方法直接检查特定键的值。
方法
存储信息
Player Data 存储信息方法与 Player Object 存储信息方法一起位于 VRC.SDKBase.Networking 命名空间中。
| 函数 | 输入 | 输出 | 备注 |
|---|---|---|---|
| GetPlayerDataStorageLimit | int | 返回 Player Data 的存储限制,以字节为单位。 | |
| GetPlayerDataStorageUsage | VRCPlayerApi target | int | 返回目标玩家上次计算的 Player Data 存储使用量。 |
| RequestStorageUsageUpdate | void | 请求计算本地玩家的 PlayerData 和 PlayerObject 存储使用量;结果通过 OnPersistenceUsageUpdated 事件到达。 |
请注意,存储信息可能随时间过期,可能需要更新。请避免频繁调用 RequestStorageUsageUpdate。
Queries(查询)
使用这些方法检索与键关联的值的更多信息。它们有助于在尝试对键执行操作之前收集关于键中内容的更多信息。
| 函数 | 输入 | 输出 | 备注 |
|---|---|---|---|
| HasKey | VRCPlayerApi player, string key | bool value | 如果该键在 PlayerData 中存在值,则返回 true |
| GetType | VRCPlayerApi player, string key | Type | 获取 PlayerData 中该键包含的值的类型 |
| TryGetType | VRCPlayerApi player, string key | out Type t, bool success | 获取 PlayerData 中该键包含的值的类型。如果该键不存在,则返回 false |
Mutators(修改器)
使用这些方法保存本地玩家的 PlayerData。无法设置远程玩家的数据。
值可以被覆盖,即使它们之前有不同的类型。写入后键无法被删除。
| 函数 | 输入 |
|---|---|
| SetString | string key, string value |
| SetBool | string key, bool value |
| SetSByte | string key, sbyte value |
| SetByte | string key, byte value |
| SetBytes | string key, byte[] value |
| SetShort | string key, short value |
| SetUShort | string key, ushort value |
| SetInt | string key, int value |
| SetUInt | string key, uint value |
| SetLong | string key, long value |
| SetULong | string key, ulong value |
| SetFloat | string key, float value |
| SetDouble | string key, double value |
| SetQuaternion | string key, Quaternion value |
| SetVector4 | string key, Vector4 value |
| SetVector3 | string key, Vector3 value |
| SetVector2 | string key, Vector2 value |
| SetColor | string key, Color32 value |
| SetColor32 | string key, Color32 value |
Accessors(访问器)
使用这些方法获取实例中任何玩家的 PlayerData。
如果键不存在,则返回该类型的默认值。例如,调用 PlayerData.GetInt() 将返回 0。
使用错误的访问器类型时也会返回默认值,例如在包含 string 的键上使用 GetInt。
如果不希望使用默认值,请使用 TryGet 或 Queries 来区分默认值和缺失的键。
| 函数 | 输入 | 输出 |
|---|---|---|
| GetString | VRCPlayerApi player, string key | string value |
| TryGetString | VRCPlayerApi player, string key | string value, bool success |
| GetBool | VRCPlayerApi player, string key | bool value |
| TryGetBool | VRCPlayerApi player, string key | bool value, bool success |
| GetSByte | VRCPlayerApi player, string key | sbyte value |
| TryGetSByte | VRCPlayerApi player, string key | sbyte value, bool success |
| GetByte | VRCPlayerApi player, string key | byte value |
| TryGetByte | VRCPlayerApi player, string key | byte value, bool success |
| GetBytes | VRCPlayerApi player, string key | byte[] value |
| TryGetBytes | VRCPlayerApi player, string key | byte[] value, bool value |
| GetShort | VRCPlayerApi player, string key | short value |
| TryGetShort | VRCPlayerApi player, string key | bool value |
| GetUShort | VRCPlayerApi player, string key | ushort value |
| TryGetUShort | VRCPlayerApi player, string key | ushort value, bool success |
| GetInt | VRCPlayerApi player, string key | int value |
| TryGetInt | VRCPlayerApi player, string key | int value, bool success |
| GetUInt | VRCPlayerApi player, string key | uint value |
| TryGetUInt | VRCPlayerApi player, string key | uint value, bool success |
| GetLong | VRCPlayerApi player, string key | long |
| TryGetLong | VRCPlayerApi player, string key | long value, bool success |
| GetULong | VRCPlayerApi player, string key | ulong |
| TryGetULong | VRCPlayerApi player, string key | ulong value, bool success |
| GetFloat | VRCPlayerApi player, string key | float |
| TryGetFloat | VRCPlayerApi player, string key | float value, bool success |
| GetDouble | VRCPlayerApi player, string key | double |
| TryGetDouble | VRCPlayerApi player, string key | double value, bool success |
| GetQuaternion | VRCPlayerApi player, string key | Quaternion |
| TryGetQuaternion | VRCPlayerApi player, string key | Quaternion value, bool success |
| GetVector4 | VRCPlayerApi player, string key | Vector4 |
| TryGetVector4 | VRCPlayerApi player, string key | Vector4 value, bool success |
| GetVector3 | VRCPlayerApi player, string key | Vector3 |
| TryGetVector3 | VRCPlayerApi player, string key | Vector3 value, bool success |
| GetVector2 | VRCPlayerApi player, string key | Vector2 |
| TryGetVector2 | VRCPlayerApi player, string key | Vector2 value, bool success |
| GetColor | VRCPlayerApi player, string key | Color |
| TryGetColor | VRCPlayerApi player, string key | Color value, bool success |
| GetColor32 | VRCPlayerApi player, string key | Color32 |
| TryGetColor32 | VRCPlayerApi player, string key | Color32 value, bool success |
示例
Persistent Jumps Counter
- Udon Graph
- UdonSharp

using TMPro;
using UdonSharp;
using VRC.SDK3.Persistence;
using VRC.SDKBase;
using VRC.Udon.Common;
public class JumpCounter : UdonSharpBehaviour
{
public TextMeshProUGUI jumpText;
private const string JumpsKey = "jumps";
public override void InputJump(bool value, UdonInputEventArgs args)
{
if (value)
{
AddJump();
}
}
public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
if (player.isLocal)
{
UpdateTextComponent();
}
}
private void AddJump()
{
var currentJumps = PlayerData.GetInt(Networking.LocalPlayer, JumpsKey);
PlayerData.SetInt(JumpsKey, currentJumps + 1);
}
private void UpdateTextComponent()
{
jumpText.text = $"Jumps: {PlayerData.GetInt(Networking.LocalPlayer, JumpsKey)}";
}
}