前言
SO的核心功能是存储,在此过程中正确的序列化必不可少。于是本节将从ISerializationCallbackReceiver和SerializeReference两方面补充序列化的使用。
ISerializationCallbackReceiver
自定义序列化/反序列化回调的接口。在开发过程中有些类不能被序列化,出于某些原因无法改动源代码,但又不得不对其序列化,例如Dictionary字典、Type类型。这时候就可以包装一个实现此接口的类,从而达到序列化的目的。
以Type类型简单举例:现有需求,多个类需要存储Type数据。由于Unity无法序列化Type类型,一般做法是,改为存储string,需要时再将其转换成Type。但考虑到有多个类,为了避免重复代码,可以将Type包装成一个可序列化的类。
[Serializable]
public class SerializableType : ISerializationCallbackReceiver
{
[SerializeField]
private string type;
[NonSerialized]
public Type typeValue;
public SerializableType(Type value)
{
typeValue = value;
}
//序列化之前
public void OnBeforeSerialize()
{
type = typeValue?.AssemblyQualifiedName;
}
//反序列化之后
public void OnAfterDeserialize()
{
if(string.IsNullOrEmpty(type))
{
typeValue = null;
return;
}
typeValue = Type.GetType(type);
}
//省略部分代码……
}
这里有几点需要注意:
- 对于不想序列化的数据,使用[NonSerialized]标记
- OnBeforeSerialize将会在Unity序列化之前被调用,这里用于将Type”序列化“为String
- OnAfterDeserialize将会在Unity反序列化之后被调用,这里用于将String”反序列化“为Type
- 实现ISerializationCallbackReceiver不会影响Unity序列化过程,只是多执行了两个回调而已
- (补充)如果实现ISerializationCallbackReceiver的类中,存储了同样实现ISerializationCallbackReceiver的成员,需要在OnBeforeSerialize时手动调用该成员的OnBeforeSerialize方法,在OnAfterDeserialize时手动调用该成员的OnAfterDeserialize方法
以此类推,很容易想到可序列化字典的实现方法:在OnBeforeSerialize时将字典的keys和values放入两个List当中,在OnAfterDeserialize时再将两个List里的内容取出组成新字典即可。
SerializeReference
按引用序列化。这个标签扩充了Unity序列化的功能,使其灵活性大幅提升,但由于诞生以来就bug不断,又存在效率问题,仍然存在争议。不过对本人来说,牺牲一点运行效率提高开发效率是值得的。
功能一:序列化引用
在SerializeReference出现之前,对于所有可以序列化的字段,Unity都采用内联方式——也就是拷贝一份嵌进去。这意味着你只能存储(组合)而不能单纯保存引用(关联)。对于图形来说,这意味着必须要把复杂的图形展开,分别存储元素和元素之间的联系。
以二叉树为例,在名为T的SO中建立公有List<Node> tree,其中有三个节点node1,node1left和node1right。每个节点数据结构中有公有的string name,[SerializeReference]Node leftNode和[SerializeReference]Node rightNode。那么打开T的文件,会发现其大概会以以下格式存储:
tree:{
guid1 name:node1 leftNode:guid2 rightNode:guid3
guid2 name:node1left leftNode: rightNode:
guid3 name:node1right leftNode: rightNode:}
本质是给对象设guid作为标识,然后记录引用对象的guid
在实际开发中,如果不能保存引用,就得额外记录引用信息,然后再做繁琐的查询和绑定。比如存储一个列表,同时想记录列表中某个关键元素,就只能记下元素信息,然后查找了。不足之处也有,就是一个序列化对象仍然无法在不同宿主(GameObject、ScriptableObject等)中共享。
功能二:支持多态和null
还是拿二叉树举例,不过这次的节点Node有两个子类,一种叫DogNode,另一种叫CatNode。如果不使用SerializeReference标签为List<Node> tree标记,就无法支持多态,那么tree中所有的节点都会被当作基类Node序列化,从而丢失数据。标记后,tree的存储格式会变为:
tree:{guid1, guid2, guid3}
在Inspector面板里,tree的每个元素都会按其实际类型绘制,方便了数据查看和配置。、
另外,没有SerializeReference标记的字段,若反序列化时值为null,Unity会为其创建默认对象(最多嵌套七层)。如string类型就会反序列化为””而不是null。按引用序列化之后,字段就可以被反序列化为null了。
代码例
public class Graph : ScriptableObject
{
[SerializeField]
[SerializeReference]
protected List<Node> nodeList;
[SerializeField]
[SerializeReference]
protected List<DogNode> dogNodes;
[SerializeField]
[SerializeReference]
protected List<CatNode> catNodes;
}
P.S.根据最新文档SerializeReference – Unity 脚本 API节选
The value assigned to a field with the SerializeReference attribute must follow these rules: Must not derive from UnityEngine.Object. For example it cannot be a ScriptableObject.
不能序列化引用SO。但本人在项目实际中,SO里序列化引用List<SO>并未出错,推测其限制不适用于List。
文章版权归作者所有








暂无评论内容