持久数据:如何保存游戏状态和设置

保存数据对任何游戏都至关重要。无论您需要保存高分、首选项还是游戏状态,Unity 都提供了多种方法--从 PlayerPrefs 到数据序列化、加密以及写入文件。
2021 年 6 月 23 日更新:作为 Unite Now 2020 的一部分,我创建了一个会议,介绍 Unity 中数据持久性的技巧。它涵盖了在 Unity 项目中保存和加载数据的一些常见方法,但绝不是一个详尽的列表。也就是说,数据序列化的方法比你所需要的还要多,每种方法都能解决特定的问题,都有自己的优缺点。本博文将介绍我在Unite Now 会议上讨论过的常用方法。
PlayerPrefs 并不是用来保存游戏状态的。不过,它们还是有用的,所以我们要讨论一下。您可以使用 PlayerPrefs 在会话之间存储播放器的首选项,例如质量设置、音频音量或其他非必要数据。PlayerPrefs 存储在设备上的某个位置,与项目分开。具体位置因操作系统而异,但通常是操作系统可全局访问和管理的地方。存储的数据是简单的键值对。由于它们很容易访问,因此对于希望打开和修改它们的用户来说并不安全,而且它们可能会被意外删除,因为它们保存在项目之外并由操作系统管理。
PlayerPrefs 的实现相对简单,只需几行代码即可完成,但它只支持浮点型(Float)、整数型(Int)和字符串型(String)值,这就给大型复杂对象的序列化带来了挑战。有决心的用户可以通过将保存的数据转换成这些基本类型所代表的某种格式来克服这一限制,但我不建议这样做,因为有更好的工具来存储数据。
public void SavePrefs()
{
PlayerPrefs.SetInt("Volume", 50);
PlayerPrefs.Save();
}
public void LoadPrefs()
{
int volume = PlayerPrefs.GetInt("Volume", 0);
}
最后,由于每个 Unity 应用程序都将其所有 PlayerPrefs 存储在单个文件中,因此不适合处理多个保存文件或云保存,这两种情况都要求您从不同的位置存储和接收保存数据。
JSON 是一种人类可读的数据格式。也就是说,人和机器都很容易理解,这既有好处也有坏处。当你能读懂保存的数据时,调试保存的数据或为测试目的创建新的保存数据就容易多了,但另一方面,玩家也很容易读懂和修改数据。如果支持修改,读取和更改数据的能力是有用的,但如果要防止作弊,这种能力则是有害的。除了这些问题之外,由于 JSON 是一种基于文本的格式,因此机器的解析成本较高。也就是说,与二进制替代方案相比,它的读取速度更慢,使用的内存更多。因此,如果您有大量数据,您可能需要考虑非文本选项。每种使用情况都不尽相同,正是这种权衡导致开发人员创建了许多其他数据格式。
JSON 已标准化,并广泛应用于许多不同的应用程序。因此,所有平台都大力支持它,这对制作跨平台游戏很有帮助。JSON 是作为网络浏览器的通信协议而开发的,因此天生就适合在网络上发送数据。因此,JSON 非常适合从服务器后台发送和接收数据。
JsonUtility是 Unity 用于序列化和反序列化 JSON 数据的内置 API。与 PlayerPrefs 类似,它也比较容易实现。不过,与 PlayerPrefs 不同的是,您必须自己将 JSON 数据保存到文件中或通过网络保存。自己处理数据存储可以轻松管理多个保存文件,因为您可以将每个文件存储在不同的位置。为了简化操作,我编写了一个基本的文件管理器,可在此示例库中找到。
需要指出的是,JsonUtility 并不是一个功能全面的 JSON 实现。如果您习惯于使用 JSON 数据,您可能会注意到缺少对特定功能的支持。如果您有兴趣比较不同 JSON 解决方案的性能,可以试试这个基准测试项目。请记住,最好尽可能在目标设备上进行测试。
与 Unity 内部序列化器一样,JsonUtility 也受到同样的限制,也就是说,如果无法在 Inspector 中序列化某个字段,就无法将其序列化为 JSON。要绕过这些限制,可以创建普通旧数据类型(或 PODS)来保存所有保存数据。保存时,将数据从运行时类型转移到 POD 中,然后保存到磁盘上。如有需要,您还可以创建自定义序列化回调,以支持 Unity序列化器默认不支持的类型。
关于 JsonUtility,EditorJsonUtility是另一个有用的工具。JsonUtility 适用于任何基于 MonoBehaviour 或 ScriptableObject 的对象,而 EditorJsonUtility 则适用于任何 Unity 引擎类型。因此,您可以在 Unity 编辑器中创建任何对象的 JSON 表示形式,也可以反其道而行之,从 JSON 文件创建资产。
除了内置的序列化选项,您还可以使用其他外部库。除非你特别需要使用基于文本的格式来提高其可读性,否则最好使用基于二进制的序列化器:
二进制工具
- MessagePack是一种高效的二进制序列化器。它性能卓越,使用相对简单。与 JSON 一样,它几乎适用于所有平台,因此您可以使用它通过网络发送数据,与后端服务器进行通信。您可以在这里了解更多信息。
- ProtoBuf和Protobuf-net是另一种类似的二进制序列化器。它还快速高效。谷歌开发它是为了替代 XML 等现有格式。与 JSON 和 MessagePack 一样,它也非常适合网络通信。
- BinaryFormatter是一个 DotNet 库,可直接以二进制格式存储对象。不过,BinaryFormatter 存在危险的安全漏洞,应避免使用。我再说一遍,不要使用 BinaryFormatter。点击此处了解有关安全风险的更多信息。
文本工具
- EasySave是 Unity Asset Store 上一款支持良好且广受欢迎的插件。它可以让你保存所有数据,而无需编写任何代码,这对初学者来说是再好不过了。它还拥有强大灵活的 API,因此也非常适合高级用户使用。它不是免费的,但如果你正在寻找一个功能齐全的开箱即用解决方案,它还是物有所值的。
- JSON.Net是适用于所有 DotNet 平台的免费开源 JSON 实现。与内置的 JsonUtility 不同,它功能齐全。不过,这也是有代价的,因为它的性能明显低于内置的 JsonUtility。标准版并不支持 Unity 的所有平台,但在 Unity 资产商店中有一个修改版,可以增加支持。
- XML是一种替代数据格式。与 JSON 一样,它相对易于人类阅读,并具有一些可能对您的特定应用程序有用的功能,如命名空间。DotNet 内置支持 XML。
提到安全,大多数人首先想到的是加密。不过,如果要在玩家设备的 Localization 上存储数据,加密技术相对容易攻克。即使没有破解加密,用户也可以使用免费工具直接在内存中操作数据。换句话说,可以放心地认为,任何存储在 Localization 的东西都是不可信的。
如果你需要真正的安全性,最好的选择是将数据保存在用户无法修改的服务器上。要做到这一点,应用程序不应该直接向服务器发送任何数据,因为用户仍然可以对其进行操作。相反,应用程序只能向服务器发送命令,让服务器更改数据,然后将结果发回给应用程序。因此,如果数据安全对您至关重要,最好尽快了解情况,因为这将影响您的项目架构。
有关序列化的更多信息,请查看手册页面。如果您想了解具体操作,请查看随附的Unite Now 会议 。