【什么是序列化】
序列化是将对象转换为二进制流的过程,反序列化是将二进制流转换为对象的过程。序列化主要解决对象的传输问题。Unity对Unity有自己的序列化机制(方法),但没有开放成API。Unity 的一些内置功能(保存和加载、Inspector 窗口、实例化和预制件)的实现需要使用序列化。
【Unity中可序列化的对象】
- 该对象必须是非static、非const、非readonly,且为public 或者具有SerializeField属性
- 可序列化的字段类型
- 原始数据类型(
int 、float 、double 、bool 、string ?等) - 枚举类型
- 具有?
Serializable ?属性的自定义结构体 - 某些 Unity 内置类型:
Vector2 、Vector3 、Vector4 、Rect 、Quaternion 、Matrix4x4 、Color 、Color32 、LayerMask 、AnimationCurve 、Gradient 、RectOffset 、GUIStyle
- 可自定义序列化类,但该类必须具有Serializable属性,且非静态、非抽象、非泛型(可继承自泛型类)
- 可序列化的容器类型:上述类型的数组、线性表List<T>
【Unity中序列化的例子】
我们可以在Inspector窗口查看或修改脚本中字段的值;在游戏运行时,我们常常发现在Inspector窗口中字段的值会代替脚本中字段的值;修改这个值我们会立马在Play窗口看到效果;停止游戏时,这个值会变回原来的值,而不是最后修改的值。
能够查看是因为这个字段被序列化了,例如常见的public string name就会自动序列化了,所以我们能在Inspector窗口查看或修改name的值,具体可以看这个例子。
常用的属性有SerializeField,HideInInspector,NonSerialized,Serializable
- SerializeField : 表示字段可被序列化。公有字段可以在Inspector窗口中看到并编辑,而私有和保护字段不行。SerializeField与private,protected结合使用可让脚本的字段在Inspector窗口中可视化编辑,同时保持它的私有性。
- HideInInspector : 将原本显示在Inspector窗口上的序列化值隐藏起来。
- NonSerialized:不被序列化且不显示
- Serializable:用于类前,表示该类可序列化
在Inspector窗口修改字段的值时,Unity 会序列化此数据,然后反序列化在 Inspector 窗口中显示数据。这个数据存储在native层。
unity其实是两层,C++层与unity控制层,因为unity是用c++编写的,但是我们自己编写的脚本是c#,就会有一个交互。当我们点击运行按钮时,先是把所有的序列化数据在内部创建,然后把他们存在c++这一层;然后清除unity控制层这边所有的内存和消息;然后加载我们编写的脚本;最后再把c++层中存储的序列化数据反序列化到unity控制层中去。
在运行时修改字段的值只是更改unity控制层上的数据,游戏运行过程中会读取这个数据,但不会保存在C++层(Native层)。
游戏停止后,会再次反序列化Native层中的数据,显示运行前没更改的那个数值,显示时不会与Unity Scripting API 通信。
Prefab是一个或多个游戏对象和组件的序列化数据,包含对所有源物体的引用和相应的修改列表。任何派生自UnityEngine.Object的对象都可被实例化,当使用Instantiate实例化一个Prefab时,会先创建一个GameObject,随后根据引用找到源物体,反序列化得到源物体,在根据修改列表修改相应的值,最后得到实例化的物体。
【Unity不支持的对象及序列化的实现】
Unity不能序列化属性、树结构、泛型、字典、高维数组、委托等,最直观的表现是在Inspector窗口中看不到。
Unity不支持空引用。遇到空引用时,Unity会自动构造一个对象来填补这个引用,如果这个引用的类型就是自身,例如在树结构中每个节点为Node类型,还有两个Node类型的子节点,那么会无限循环的构造对象。尽管Unity对这个循环的次数做了限制(7次),但会影响运行时的性能。解决方法是自己创建一个不用的对象。
Unity不支持多态。如果具有一个Animal基类,在某个脚本中有字段public Animal[] animals,随后放入的是继承Animal类的Dog、Cat、Fish类的实例,那么在序列化后得到Animal实例,不能识别出派生类。解决方法是基类要继承UnityEngine.Object或Monobehaviour。
对于高维数组,将其低维化。,即底层采用一维数组来替代。
对于字典,key和value各自存储成List,运行时用字典,序列化时用数组。
对于泛型类,用一个新类将其封装并用?[Serializable] ?修饰新类。
对于不带返回值的委托,可以用?UnityEvent ?来序列化。
对于更复杂的情况,例如树结构,需实现ISerializationCallbackReceiver。
using UnityEngine;
using System.Collections.Generic;
using System;
//直接序列化将导致性能问题
public class VerySlowBehaviourDoNotDoThis : MonoBehaviour {
[Serializable]
public class Node {
public string interestingValue = "value";
//下面的字段使序列化数据变得巨大,
//因为它引入了"类周期"。
public List<Node> children = new List<Node>();
}
//这将经过序列化
public Node root = new Node();
void OnGUI() {
Display (root);
}
void Display(Node node) {
GUILayout.Label ("Value: ");
node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width(200));
GUILayout.BeginHorizontal ();
GUILayout.Space (20);
GUILayout.BeginVertical ();
foreach (var child in node.children)
Display (child);
if (GUILayout.Button ("Add child"))
node.children.Add (new Node ());
GUILayout.EndVertical ();
GUILayout.EndHorizontal ();
}
}
using System.Collections.Generic;
using System;
//避免直接序列化,先处理成Unity支持的形式
public class BehaviourWithTree : MonoBehaviour, ISerializationCallbackReceiver {
// 在运行时使用的 Node 类。
//此类位于 BehaviourWithTree 类的内部,不会被序列化。
public class Node {
public string interestingValue ="value";
public List<Node> children = new List<Node>();
}
// 我们将用于序列化的 Node 类。
[Serializable]
public struct SerializableNode {
public string interestingValue;
public int childCount;
public int indexOfFirstChild;
}
//用于运行时树表示的根节点。不序列化。
Node root = new Node();
//这是我们提供给 Unity 进行序列化的字段。
public List<SerializableNode> serializedNodes;
public void OnBeforeSerialize() {
//Unity 即将读取 serializedNodes 字段的内容。
// 现在必须"及时"将正确的数据写入该字段。
if (serializedNodes == null) serializedNodes = new List<SerializableNode>();
if (root == null) root = new Node ();
serializedNodes.Clear();
AddNodeToSerializedNodes(root);
// 现在 Unity 可自由地序列化这个字段,我们应该在稍后反序列化时
// 找回预期的数据。
}
void AddNodeToSerializedNodes(Node n) {
var serializedNode = new SerializableNode () {
interestingValue = n.interestingValue,
childCount = n.children.Count,
indexOfFirstChild = serializedNodes.Count+1
}
;
serializedNodes.Add (serializedNode);
foreach (var child in n.children)
AddNodeToSerializedNodes (child);
}
public void OnAfterDeserialize() {
//Unity 刚刚将新数据写入 serializedNodes 字段。
//让我们用这些新值填充我们的实际运行时数据。
if (serializedNodes.Count > 0) {
ReadNodeFromSerializedNodes (0, out root);
} else
root = new Node ();
}
int ReadNodeFromSerializedNodes(int index, out Node node) {
var serializedNode = serializedNodes [index];
//将反序列化的数据传输到内部 Node 类
Node newNode = new Node() {
interestingValue = serializedNode.interestingValue,
children = new List<Node> ()
}
;
// 以深度优先的方式读取树,因为这正是我们写入树的方式。
for (int i = 0; i != serializedNode.childCount; i++) {
Node childNode;
index = ReadNodeFromSerializedNodes (++index, out childNode);
newNode.children.Add (childNode);
}
node = newNode;
return index;
}
// 此 OnGUI 在 Game 视图中绘制出节点树,其中包含用于添加新节点作为子项的按钮。
void OnGUI() {
if (root != null)
Display (root);
}
void Display(Node node) {
GUILayout.Label ("Value: ");
// 允许修改节点的"有用值"。
node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width(200));
GUILayout.BeginHorizontal ();
GUILayout.Space (20);
GUILayout.BeginVertical ();
foreach (var child in node.children)
Display (child);
if (GUILayout.Button ("Add child"))
node.children.Add (new Node ());
GUILayout.EndVertical ();
GUILayout.EndHorizontal ();
}
}
【Json序列化】
还可以采用Json格式对类进行序列化,使用JsonUtility?类可在 Unity 对象与 JSON格式之间来回转换。基准测试表明,JsonUtility比流行的 .NET JSON 解决方案要快得多。使用方式参考下面的代码。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class test : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Data date = new Data();
date.num = 1;
date.home = "sdfew";
date.name = "enternalstar";
string json = JsonUtility.ToJson(date);//将对象Json序列化
Data getData = JsonUtility.FromJson<Data>(json);//反序列化再得到对象
Data newData = new Data();
newData.name = "saff";
newData.num = 3;
newData.home = "safjoi";
JsonUtility.FromJsonOverwrite(json, newData);//将其他Data对象的数据覆盖到newData上
}
[Serializable]//自定义一个需要序列化的类
public class Data
{
public int num;
public string home;
public string name;
}
}
【参考】
Unity - Manual: Unity architecture (unity3d.com)
深入Unity序列化 - 知乎 (zhihu.com)
关于序列化Serializable - 简书 (jianshu.com)
|