序列化(serialization)在计算机科学的资料处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。
序列化的发生
当我们用(UnityEngine.Object.Instantiate())此方法去生成一个 UnityObject 时候,就经历了序列化和反序列化的操作。
Original Object —serialize—> mid-data —deserialize—> Spawned Object
序列化使用场景
保存相关资产
我们的许多 UnityObject 资产 scene, prefab, material 都是以文本文件(YAML)的形式保存在项目中。编辑器时,会在 Library 中存放’中间物体’。
我猜测,编辑器时会使用这些’中间物体’以减少实际序列化的步骤来加速运行。
当我们将磁盘里的 Asset 加载到游戏的运行时里的 UnityObject 的过程,是一个反序列化操作,把YAML反序列化成 UnityObject。
在编辑器时,我们会修改脚本,改变一些数据类型。把字段名称修改后,原本序列化的值丢失了。我猜是因为项目在 Refresh 的时候,会先把所有必要的数据都序列化存入’中间物体’,然后等代码编译完,生成了新的类型,再尝试把原本的’中间物体’用新的类型反序列化生成新物体并在序列化生成新的’中间物体’。这个过程中由于是使用新类型进行反序列化了,自然会抛弃新类型中没有的(不同名且类型不匹配的)数据。引擎C#层重载 HotReloading 会报错但是有可能凑合能跑估计也是这个原因,所以 HotReloading 实际上是为了让我们快速修改业务逻辑看看有无修好的。
FormerlySerializedAs(PROPERTY_NAME) 这个特性可以解决上面说的这个问题,但其实就是要求这个字段做序列化相关操作时沿用旧的字段名(PROPERTY_NAME)。这样在 prefab 中序列化下来记录的字段名一直都是那个旧的字段名。
编辑器的字段绘制
当我们对字段做做编辑器绘制的时候,也会有序列化以及反序列化的操作。
Inspector —update changes—> Serialized Properties —deserialize—> Spawned Object
Inspector <—update view— Serialized Properties —serialize—> Spawned Object
UnityObject 中的字段值会被序列化到 Serialized Properties 集合中,然后编辑器绘制相关的代码拿到序列化后得到的数据集合,再把他们提出来并根据数据类型画不同的样式。
下面是一个 PropertuDrawer 的例子,在绘制方法的参数中有 SerializedProperty,这个就是用于存放 UnityObject 中被序列化字段的 wrapper。
1 | [ ] |
对 UnityObject 引用的序列化
哦我的老兄,这个大家都很熟悉了。以文本的形式打开我们的 prefab 康康。摘一段
1 | --- !u!114 &7941049597127225971 // !u!CLASS_TYPE &OBJECT_FILE_ID |
首行表明了该 UnityObject 的类型以及它在这个 Prefab 中的唯一标识ID。
m_Script 这一行表明了这个物体是个 .cs 脚本。
我们还能发现有些字段只记录 { FileID }, 有些却要记录三个参数 {fileID, guid, type} 。记录三个参数的表明的是这个 prefab 内的物体的外部来源。听起来很别扭,我说详细点吧,就好比说一个 prefab A 中包含了一个 prefab B,那么 B 物体就是需要在 prefab A 中以如上方式记录。 其中 guid 是 prefab B 所在(定义处)的资源文件的 guid, FileID 则是这个物体在资源内的唯一ID,type 是这个物体的资源类型。
所以 UnityObject 引用的序列化是依靠以文本的形式记录ID完成的。
对空引用(null)的支持
空引用只支持 UnityObject 捏,如果是我们自定义的 C# class, 序列化时会生成默认的给他。这个时候如果有疯狂嵌套的 C# class就会爆炸了,因为他会一直往里层生成默认值给这些可序列化的 C# Class object。