irpas技术客

理解Unity中的序列化_永恒星_unity 序列化对象

大大的周 3431

【什么是序列化】

序列化是将对象转换为二进制流的过程,反序列化是将二进制流转换为对象的过程。序列化主要解决对象的传输问题。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窗口查看或修改脚本中字段的值;在游戏运行时,我们常常发现在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

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)


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Unity #序列化对象 #序列化主要解决对象的传输问题