07Serialization相关
Serialization相关
本文主要记录了UnitySerialization的一些细节。 大部分Unity书籍都有介绍到Serialization(序列化)的相关功能。官方文档也有详细的介绍:
如Serialization规则: https://docs.unity3d.com/Manual/script-Serialization.html
SerializationReference规则: https://docs.unity3d.com/ScriptReference/SerializeReference.html
Serialization规则:
根据官方文档,序列化规则会直接作用在C#类的字段上,以方便快速的将对象数据序列化成之久数据。对于Prefab,场景,数据类等都是必须有的功能。相对应的,unity对所对应的字段需要满足一定程度的规则。
Serialization大部分情况主要作用在MonoBehaviour和ScriptableObject这两个类的环境下。也就是说继承该类型的子类的实例,会被序列化成持久化数据。最常见的就是ScriptableObject类型的序列化。
对于类字段来说:
- public字段,或者又SeializeField注解的会被序列化。
- static,const,readonly字段不会被序列化。
对字段的类型也有一定要求:
- C#原始数据类型:
int,float,double,bool,string,etc
。 - 枚举类型:需要32位以内的格式。
- 固定大小的buffer。
- Unity内置类型:
Vector2,Vector3,Rect,Matrix4x4,Color,AnimationCurve
。 - 带有Serializable注解的自定义Struct。
- 带有Serializable注解的自定义Class。
- UnityEngine.Object的子类。
- 以上述类型为参数的,数组或者List类型。
额外的说明
- 对于嵌套类型,多维数组,Dictionary等类型是不支持序列化的。也不会反射显示再Inspector面板上面。
- 可以自定义序列化,反序列化流程。只要实现
ISerializationCallbackReceiver
接口即可。其中有两个方法,OnBeforeSerialize
会在序列化前调用触发。OnAfterDeserialize
会在反序列化后触发。通过这两个方法可以自定义一些复炸的数据结构方式。
这里需要注意到的是:
对于序列化类型来说,文件名字必须和类名相同,否则序列化时可以正常运行。反序列化时会因为找不到对应类型,而初始化会默认值。
对于UnityEngine.Object的子类来说,只要是对应类型的即可序列化。但是自定义类型,没有继承自UnityEngine.Object的类型需要标注Serializable。
例如:
|
|
创建对应的ScriptableObject会发现Inspector面板如下图。其中不存在field1,只有field2可以展开,填写对应的数据字段。而field3则是一个可以放入其他ScriptableObject的引用类型值。

这就是因为未标记Serializable的字段不认为可以被序列化,再反序列化的时候也不会被处理。
SerializeReference
在上面我们已经可以看到对于自定义类型的序列化,和,继承UnityEngine.Object类型的序列化是有一点不太一样的。一个是直接展示暴露编辑字段,一个是引用对象处理。这里就涉及到Unity序列化的两种方式。
- 对于通常默认的序列化,称之为Inline Serialization。unity会将对象的值直接序列化成数据,放在prefab对应字段处。当反序列化时,直接从对应字段处反序列化出来。
- 还有一种称之为SerializeReference。即引用序列化。会给序列化后的值赋值给一个引用ID,并将数据放在对应引用ID下面。这样当反序列化后,引用相同ID的字段,会反序列化同一对象。
举例来说明,对于:
|
|
当我们运行FillField
方法是,其会给field1和field2注入一个相同的类型。此时看Inspector面板,会发现field2显示Reference to field1表示是对field1上对象的一个引用。当修改field1中str字段时,会发现,field2中字段也跟着改变。
但是当我们保存,触发反序列化之后会发现field1和field2变成了两个不相干的对象。这就是每个对应字段都序列化到了自己的inline字段中。
如果修改成如下结构:
|
|
则会发现,
对于情况1:触发反序列化后,两个任然指向同一个对象。这是因为序列化后两个字段都指向同一个引用编号。进而反序列化同一份数据,并且根据编号,两者引用同一个值。 对于情况2:触发反序列化后,会发现两个是不同对象。并没有说,field2直接引用到field1上面。这是因为field1是inline数据,field2是指向引用id。并不是说直接指向field1,所以还是会产生两个不同数据。
通过prefab数据我们可以看到这个结果:
|
|
可以看到field1是直接内嵌序列化了一段数据。而对于field2则是记录了一个rid。并且在references中又对应rid对应的类型和数据。
这就是Reference和直接数据序列化上的区别。这里就说到对于Unity来说
- 继承子UnityEngine.Object类型的都是引用序列化。也就是Monobehaviour,Transform,animation等等都是以Reference的方式序列化的。
- 其他类型,除非特别声明则是按值内嵌序列化。
同时官方文档也给出了使用SerializeReference的场景。
- 如果你希望多个字段引用同一份数据,可以将其全部声明成SerializReference。
- 如果你希望使用字段的多态特性,则可以申明为SerializeReference。 使用多态特性,即显示的字段,序列化后的数据,是该字段实际类型的数据结构。如果不适用引用序列化,那么其会按照字段申明的类型来进行值序列化,这样就会丢失子类型数据。
- 你希望序列化null值。对于值类型来说,并不存在null值结构。
同时对于值序列化来说其效率是高于SerializeReference的。对于存储,内存占用,加载时间等来说都是如此。
而对SerializeReference的使用来说,伴随着跟Unity序列化一样的限制结构。前面已经看到Serialization的字段有着很多限制,同时可以思考,这些字段类型,尤其是Array和List的泛型参数,可以是继承UnityEngine.Object类型的。所以SerializeReference也有可以使用的场景,和不能的情况。
- Unity支持对Array以及List类型的引用序列化。对于这些类型,SerializeReference注解相当于作用在其容器中的每个元素上面。但是你不可以申明一个System.Object类型并赋值一个Array或List以期望其按照对应方式序列化。
- 对于继承子UnitEngine.Object类型的不可以注解SerializeReference。
- 不可以是C#的值类型。
- 不可以是Serialization中不可以序列化的容器结构。
Odin情况下的Serialization
Odin插件也提供了一套序列化方式,这很大一部分原因是因为Inspector的编辑操作依赖于对象类型上的序列化,只有序列化成数据才比较方便去做对应的UI绘制,以及数据编辑操作。
Odin提供了几种方式来使用Odin序列化。可以看出其实际也是在Unity原有的管线接口上,进行了一层自己的封装。
- 方式1: 直接继承SerializedMonoBehaviour或者SerializedScriptableObject。这个基类里面有对序列化方式的支持。
- 方式2:
实现ISerializationCallbackReceiver。并且在对应接口里面桥接实现Odin的序列化操作。为此Odin提供了一个序列化Ulility
UnitySerializationUtility
。其需要当前序列化的类,以及一个SerializationData对象。
Odin的序列化主要就是针对上面所不支持的一些情景进行了填充。操作方式就是将不支持的数据结构,转化成Unity支持序列化的数据结构来进行存储。
- 对Dictionary的支持。