您想找什么?
Engine & platform

Unity 2021 LTS中的SerializeReference改进

MARTIN BARRETTE Software Engineering Manager
Jul 11, 2022|11 Min
Unity 2021 LTS中的SerializeReference改进
为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。

在最新的LTS版本中,多态序列化改进了用户协作和API访问,并更精细地处理缺失的类型。

使用 SerializeReference 属性,您可以将分配给字段的对象序列化为引用,而不是按值序列化。被引用的对象被称为“托管引用”。带有SerializeReference特性的字段主要用于支持多态(polymorphism)和空值——最近,Unity 2021 LTS还为托管引用推出了Stable ID,它可以更精细地处理类型缺失、改进API访问。在这篇博客中,我们将详细介绍这些改动及其带来的直接收益。

Stable ID

托管引用将被保存在主对象的序列化数据中,这个对象通常为一个Unity对象(例如从MonoBehaviour或ScriptableObject衍生来的类)。为了保存,我们会为每个托管引用对象分配一个唯一的ID。

具有SerializeReference特性的字段会在序列化时保存被引用对象的ID。托管引用本身会被保存在一个叫做ManagedReferenceRegistry的列表中,它是主对象序列化数据的一部分。

在Unity 2019和2020 LTS中,对象的ID会在保存时根据场景内容的深度(距屏幕的远近)进行分配。这种方法的主要缺点是,任何小的改动,如重新排列某个数组的元素,都可能极大地影响文件。文件的大量改动很可能导致合并冲突,造成难以解决的协作困难。

因此,我们推出了Stable ID。Stable ID能保证对象所分配的唯一ID会在后续的保存和加载过程中被保留下来。换句话说,改变主对象托管引用的对象并再次保存将不会改变ID。

你可以在下方例子种了解Stable ID的价值:

序列化源代码

该例子创建了一组托管引用的数组,再交错填充进Sandwich和Fruit类的实例。数组的内容可以在LunchBox1.asset文件中查看。

示例检视面板

将第一条引用移至列表的最后将修改底部的资产文件。下方截取自对比工具的截图展示了新版本与2021.3之间的差异,新版本的编辑更为容易,因为数组对象的ID与数组的排序无关。

2020.3:

2020.3 数组在 Unity 中的排序

2021.3:

2021.3 数组在 Unity 中的排序
用独特的ID让协作更流畅

除了减少对Unity文件的改动外,Stable ID更主要的作用是解决常见的协作挑战。在先前的版本中,当有两名用户在同一主对象上添加管理引用对象,引用对象的ID都是相同的,这就使得合并变得困难(特别是当一个托管引用对象被一个及以上的字段引用时)。而从Unity 2021开始,对象ID是根据时间和系统信息以哈希计算得出,可以近乎完美避免这种冲突。对于更高级的场景,你甚至可以通过调用 SerializationUtility.SetManagedReferenceIdForObject 来覆盖默认 ID 分配系统。

处理类型缺失

SerializeReference包括了对多态的支持,意味着同一个字段可以被分配给由被引用对象衍生出来的类的实例。事实上,我们支持的字段类型为“System.Object”,这是每个C#类的根基类。但这样一来,即使项目缺失了原先保存到场景或资产中的类定义,我们仍可以成功编译项目。有时,类会在源文件被删除、类被重新命名或被转移到另一个程序集时丢失。

而在加载一个SerializedReference主对象时,系统需要检查每个托管引用对象的类型名称,并解析回一个有效的类,才能将其实例化。在以前的Unity版本中,类的缺失会使整个“host”对象出错,引擎不会继续加载任何尚未出错的托管引用对象。因此,如果某个“host”包括了一个包含15个托管引用对象的数组,但凡有一个对象解析失败,那么你就无法在检视器中看到任何东西。你只会在控制台中看到一条错误信息——即便主对象在检查时仍不会被直接标记为出错——而所有的改动都会被悄悄地舍弃。

现在,在Unity 2021中,我们会把所有可加载的托管引用对象实例化,再将缺失的对象替换为空值。用户有机会进一步观察主对象的状态,并解决类型缺失的问题。并且,如果缺失的类型在主对象加载时被恢复,那么被引用对象会在下一次域重载触发时恢复,所有引用它的字段也将被正确解析。

下方就是缺失类型的对象在检视器里的样子:

在2020.3里,Fruit类不见了,但检视器却没有显示任何数组元素,也没有任何关于出错的提示:

在2021.3里,检视器会发出警告,缺矢的Fruit对象将被显示为空,而Sandwich对象仍会出现:

控制台内与类型缺失有关的报错也经过了更新,减少其重复性——报错信息现在只会识别有哪些对象缺失了类型。

这是2020.3的一条报错信息:

相比一下2021.3的警告信息:

预制件

在改进了ID系统后,外加上对预制件托管应用的改动,现在的托管引用对象都会具备一种“粘性”(即与字段绑在了一起)。在过去,PropertyModifications会根据字段的第一条属性路径进行定位。这意味着,一旦路径发生变化(例如数组重新排序),那么PropertyModification将丢失原本的托管引用,且无法解决该问题。自Unity 2021起,PropertyModifications会使用带有Stable ID的路径来引用托管引用对象,如managedReferences[ ⁇ ].myString。这就可以保证某个托管引用对象能一直保留覆盖后的值,无论它被移到哪个位置。

改进后的API支持

Unity API中增加了一个新类SerializationUtility,以公开与SerializeReference相关的功能。比如,SerializationUtility.ClearAllManagedReferencesWithMissingTypes() 可用于删除对缺失类型的引用,例如,在不计划恢复缺失类型时,从主机中删除警告状态。

我们还完善了使用CustomEditors(自定义编辑器)时调取托管引用的API,包括读取SerializedProperty.managedReferenceValue的选项。

我们为新方法的引用提供了代码样例,并且还为序列化相关的引用话题添加了更多细节。

使用SerializeReference的现有项目仍然可以在新版本中顺利加载,新版序列化代码向下兼容旧版托管引用格式。一般来说,只使用SerializeReference的用户不需要深入了解Stable ID的概念或新的API方法。不过,这些“引擎盖”下的改进将有利于典型的,尤其是多人协作时的引擎应用。

希望看过本文的你已经迫不及待要探索这新功能了。在序列化团队继续为所有Unity用户强化引擎功能期间,欢迎各位前往这个专门的论坛贴为我们提供更多的反馈、发起更多的讨论,谢谢大家!