深入Unity序列化

导语

序列化和持久化是游戏开发入门后绕不开的点。本文以Unity引擎的游戏开发为例讲讲遇到的坑和经验,适用于游戏客户端开发

引子

在软件开发中,序列化和持久化是两个重要的概念,尽管它们常常被混淆。以下是二者的主要差异:

序列化
  • 定义:序列化是将对象转化为某种格式(如二进制、JSON、XML、YAML、CSV等),以便于传输或存储。
  • 目的:主要解决对象的传输问题,使得对象能够在网络中发送或在不同的系统之间共享。
  • 应用场景:常用于网络通信、远程调用、数据交换等场景。
持久化
  • 定义:持久化是将内存中的数据以某种方式(如文件I/O、数据库)保存到外部存储(如硬盘),以便于后续使用。
  • 目的:主要解决对象的存储问题,确保数据在程序结束后仍然可用。
  • 应用场景:常用于数据存储、应用状态保存、用户数据管理等场景。

总结

一言以蔽之,序列化技术侧重于解决对象的传输问题,而持久化技术侧重于解决对象的存储问题。理解这两者的区别有助于在开发过程中选择合适的技术和方法。

干货:Unity中的序列化机制

Unity引擎对 UnityEngine.Object 有自己独特的序列化机制,但并没有开放成API,且存在一些无法序列化的类型,如泛型、字典、高维数组和委托等。这些类型的字段在编辑器的 Inspector 中无法显示,影响了开发者的使用。

Unity序列化的更新
  • Unity 2019.3:引入了对引用类型的序列化支持,可以在 Inspector 中显示接口类型的成员,只需使用 [SerializeReference] 属性。
解决无法序列化类型的折中方法
  1. 高维数组:将其低维化,使用一维数组替代。
  2. 字典:将 key 和 value 各自存储为 List,运行时使用字典,序列化时使用数组。
  3. 泛型类:用一个新类将其封装,并用 [Serializable] 修饰新类。
  4. 委托:不带返回值的委托可以用 UnityEvent 来序列化。注意使用 UnityEvent<T> 时需参考特定方法处理。带返回值的委托的解决方案较复杂,后续再讨论。
Unity序列化的常见问题
  • 空引用:当类 A 中有类 B 的空引用时,Unity 会自动构造 B 的对象来填补空引用。如果 B 类有多个构造函数,可能导致序列化问题。若 B 是 A 的子类,可能会导致无限循环,Unity 会限制深度,但会造成性能问题。

  • 多态:在 List<BaseClass> 中,实际指向的对象是派生类,但 Unity 只会序列化基类的信息,导致信息丢失。

自定义序列化
  • ISerializationCallbackReceiver:Unity 提供了该接口,通过实现 OnBeforeSerializeOnAfterDeserialize 方法,可以自定义序列化和反序列化过程。官方示例展示了如何处理字典的序列化。

  • C# 自定义序列化:C# 也有类似的接口 ISerializable,可以通过实现该接口来自定义序列化和反序列化过程。

其他序列化方案

如果不想依赖 Unity 内置的序列化机制,常见的解决方案包括:

  • JSON:易于阅读和调试,广泛使用于数据交换。
  • XML:结构化数据,适合需要复杂数据结构的场景。
  • YAML:人类可读性强,适合配置文件。
  • 二进制:高效存储,适合性能要求高的场景。

总结

Unity的序列化机制虽然强大,但也有其局限性。了解这些限制和解决方案,可以帮助开发者更有效地管理数据的序列化和持久化,确保在开发过程中减少潜在的问题。

JSON方案

自Unity 5.3起,Unity引擎内置了JSON工具 JsonUtility,使得开发者可以方便地进行JSON序列化和反序列化。除了Unity自带的工具,常用的第三方库还包括 Json.NetLitJson,这些库在性能上通常优于 .NET 自带的 DataContractJsonSerializer,尤其是 JsonUtility,其速度快且产生的垃圾回收(GC)较少。

JsonUtility的特点
  • 速度JsonUtility 在性能上表现优异,适合需要高效序列化的场景。
  • 内建类型支持:对Unity引擎内建类型(如 Vector3)的支持较好,这是其他JSON库无法直接做到的。
  • 多态支持:对于多态的支持较为有限。Unity官网提供了一种解决方案,即进行两次解码:
    1. 首先使用 JsonUtility.FromJson<BASE_CLASS> 解码基类中的公有字段。
    2. 然后利用包含的子类信息,重新进行解码 JsonUtility.FromJson<SUB_CLASS>

这种方法虽然可行,但给人一种“做了一半”的感觉。相比之下,Json.Net 对多态的支持更为完善。

YAML方案

YAML(YAML Ain’t Markup Language)是一种对人友好的数据序列化语言,具有良好的可读性和编辑性。YAML 是 JSON 的超集,意味着 YAML 解析器可以解析 JSON 格式的数据。

YAML的优势
  • 可读性:YAML 的可读性不逊色于 XML,且优于 JSON,编辑起来更为方便和健壮。
  • 灵活性:YAML 支持注释、自引用和复杂数据类型,适合描述复杂的结构。
  • 跨平台支持:虽然 YAML 在跨平台支持上不如 JSON,但其历史因素使得它在某些场景下仍然非常有用。

在Unity中,YAML 被用来描述复杂的场景结构,尽管Unity并未公开这种序列化方法的API。如果希望在Unity中使用YAML作为序列化格式,可以从Unity商店下载插件 YamlDotNet,该插件支持移动平台和PC平台。使用上也相对方便,特别是它只会序列化具有 getset 的属性类型。

总结

在选择序列化方案时,开发者可以根据项目需求和性能要求来选择合适的工具。JsonUtility 提供了快速且高效的序列化方式,适合大多数Unity项目;而YAML则在可读性和灵活性上具有优势,适合需要复杂数据结构的场景。无论选择哪种方案,了解其特点和限制都能帮助开发者更好地管理数据的序列化和持久化。

二进制方案

在数据序列化中,二进制方案通常被认为是性能最优的选择,尽管它在兼容性方面可能存在一些顾虑。使用二进制格式的主要优势在于其高效的存储和快速的读写速度。然而,二进制序列化的一个常见问题是,当数据结构发生变化(如字段的增删改)时,如何处理这些变化。

BinaryFormatter的特点
  • 容错性:经过测试,BinaryFormatter 在字段的增删改时表现出较好的容错性。在许多情况下,它能够尽可能地将二进制数据解析到对应的字段上,而不会导致程序崩溃。这可能是因为C#的序列化机制中包含了元信息(meta information),使得它能够根据这些信息来处理字段变化。
  • 元信息:理论上,如果JSON序列化也能包含类似的元信息,那么它在处理字段变化时也能达到类似的效果。

使用 BinaryFormatter 的具体方法可以参考微软的官方文档。

持久化的讨论

在Unity中,持久化数据的方式有很多,初学者通常会使用 DontDestroyOnLoad 来修饰游戏对象,以实现跨场景的数据持久化。此外,PlayerPrefs 也常用于保存简单的键值对数据,以便在下次启动时读取。

ScriptableObject的持久化
  • ScriptableObject:继承自 ScriptableObject 的类对象可以被Unity序列化并持久化为文件。这种方式不需要依附于 MonoBehaviour,避免了将数据存储在 GameObject 上并制作成Prefab的复杂过程。同时,ScriptableObject 还能够保持资源索引关系。
  • 只读性:需要注意的是,在客户端运行时,ScriptableObject 对象是只读的,无法进行写操作。因此,如果需要持久化这些对象(例如在开发插件时),通常需要将 ScriptableObject 先序列化为JSON或二进制格式,然后存储为文件。

总结

在选择数据序列化方案时,开发者需要权衡性能、兼容性和易用性。二进制方案在性能上具有明显优势,但在兼容性方面需要谨慎处理。Unity提供了多种持久化数据的方式,开发者可以根据具体需求选择合适的方案。对于需要在运行时进行数据持久化的情况,使用 ScriptableObject 结合序列化技术(如JSON或二进制)是一个有效的解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值