当前位置:   article > 正文

理解Unity中的序列化_unity序列化有什么用

unity序列化有什么用

【什么是序列化】

序列化是将对象转换为二进制流的过程,反序列化是将二进制流转换为对象的过程。序列化主要解决对象的传输问题。Unity对Unity有自己的序列化机制(方法),但没有开放成API。Unity 的一些内置功能(保存和加载、Inspector 窗口、实例化和预制件)的实现需要使用序列化。

【Unity中可序列化的对象】

  • 该对象必须是非static、非const、非readonly,且为public 或者具有SerializeField属性
  • 可序列化的字段类型
    • 原始数据类型(intfloatdoubleboolstring 等)
    • 枚举类型
    • 具有 Serializable 属性的自定义结构体
    • 某些 Unity 内置类型:Vector2Vector3Vector4RectQuaternionMatrix4x4ColorColor32LayerMaskAnimationCurveGradientRectOffsetGUIStyle
  • 可自定义序列化类,但该类必须具有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。

  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System;
  4. //直接序列化将导致性能问题
  5. public class VerySlowBehaviourDoNotDoThis : MonoBehaviour {
  6. [Serializable]
  7. public class Node {
  8. public string interestingValue = "value";
  9. //下面的字段使序列化数据变得巨大,
  10. //因为它引入了"类周期"。
  11. public List<Node> children = new List<Node>();
  12. }
  13. //这将经过序列化
  14. public Node root = new Node();
  15. void OnGUI() {
  16. Display (root);
  17. }
  18. void Display(Node node) {
  19. GUILayout.Label ("Value: ");
  20. node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width(200));
  21. GUILayout.BeginHorizontal ();
  22. GUILayout.Space (20);
  23. GUILayout.BeginVertical ();
  24. foreach (var child in node.children)
  25. Display (child);
  26. if (GUILayout.Button ("Add child"))
  27. node.children.Add (new Node ());
  28. GUILayout.EndVertical ();
  29. GUILayout.EndHorizontal ();
  30. }
  31. }

  1. using System.Collections.Generic;
  2. using System;
  3. //避免直接序列化,先处理成Unity支持的形式
  4. public class BehaviourWithTree : MonoBehaviour, ISerializationCallbackReceiver {
  5. // 在运行时使用的 Node 类。
  6. //此类位于 BehaviourWithTree 类的内部,不会被序列化。
  7. public class Node {
  8. public string interestingValue ="value";
  9. public List<Node> children = new List<Node>();
  10. }
  11. // 我们将用于序列化的 Node 类。
  12. [Serializable]
  13. public struct SerializableNode {
  14. public string interestingValue;
  15. public int childCount;
  16. public int indexOfFirstChild;
  17. }
  18. //用于运行时树表示的根节点。不序列化。
  19. Node root = new Node();
  20. //这是我们提供给 Unity 进行序列化的字段。
  21. public List<SerializableNode> serializedNodes;
  22. public void OnBeforeSerialize() {
  23. //Unity 即将读取 serializedNodes 字段的内容。
  24. // 现在必须"及时"将正确的数据写入该字段。
  25. if (serializedNodes == null) serializedNodes = new List<SerializableNode>();
  26. if (root == null) root = new Node ();
  27. serializedNodes.Clear();
  28. AddNodeToSerializedNodes(root);
  29. // 现在 Unity 可自由地序列化这个字段,我们应该在稍后反序列化时
  30. // 找回预期的数据。
  31. }
  32. void AddNodeToSerializedNodes(Node n) {
  33. var serializedNode = new SerializableNode () {
  34. interestingValue = n.interestingValue,
  35. childCount = n.children.Count,
  36. indexOfFirstChild = serializedNodes.Count+1
  37. }
  38. ;
  39. serializedNodes.Add (serializedNode);
  40. foreach (var child in n.children)
  41. AddNodeToSerializedNodes (child);
  42. }
  43. public void OnAfterDeserialize() {
  44. //Unity 刚刚将新数据写入 serializedNodes 字段。
  45. //让我们用这些新值填充我们的实际运行时数据。
  46. if (serializedNodes.Count > 0) {
  47. ReadNodeFromSerializedNodes (0, out root);
  48. } else
  49. root = new Node ();
  50. }
  51. int ReadNodeFromSerializedNodes(int index, out Node node) {
  52. var serializedNode = serializedNodes [index];
  53. //将反序列化的数据传输到内部 Node 类
  54. Node newNode = new Node() {
  55. interestingValue = serializedNode.interestingValue,
  56. children = new List<Node> ()
  57. }
  58. ;
  59. // 以深度优先的方式读取树,因为这正是我们写入树的方式。
  60. for (int i = 0; i != serializedNode.childCount; i++) {
  61. Node childNode;
  62. index = ReadNodeFromSerializedNodes (++index, out childNode);
  63. newNode.children.Add (childNode);
  64. }
  65. node = newNode;
  66. return index;
  67. }
  68. // 此 OnGUI 在 Game 视图中绘制出节点树,其中包含用于添加新节点作为子项的按钮。
  69. void OnGUI() {
  70. if (root != null)
  71. Display (root);
  72. }
  73. void Display(Node node) {
  74. GUILayout.Label ("Value: ");
  75. // 允许修改节点的"有用值"。
  76. node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width(200));
  77. GUILayout.BeginHorizontal ();
  78. GUILayout.Space (20);
  79. GUILayout.BeginVertical ();
  80. foreach (var child in node.children)
  81. Display (child);
  82. if (GUILayout.Button ("Add child"))
  83. node.children.Add (new Node ());
  84. GUILayout.EndVertical ();
  85. GUILayout.EndHorizontal ();
  86. }
  87. }

【Json序列化】

还可以采用Json格式对类进行序列化,使用JsonUtility 类可在 Unity 对象与 JSON格式之间来回转换。基准测试表明,JsonUtility比流行的 .NET JSON 解决方案要快得多。使用方式参考下面的代码。

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. public class test : MonoBehaviour
  6. {
  7. // Start is called before the first frame update
  8. void Start()
  9. {
  10. Data date = new Data();
  11. date.num = 1;
  12. date.home = "sdfew";
  13. date.name = "enternalstar";
  14. string json = JsonUtility.ToJson(date);//将对象Json序列化
  15. Data getData = JsonUtility.FromJson<Data>(json);//反序列化再得到对象
  16. Data newData = new Data();
  17. newData.name = "saff";
  18. newData.num = 3;
  19. newData.home = "safjoi";
  20. JsonUtility.FromJsonOverwrite(json, newData);//将其他Data对象的数据覆盖到newData上
  21. }
  22. [Serializable]//自定义一个需要序列化的类
  23. public class Data
  24. {
  25. public int num;
  26. public string home;
  27. public string name;
  28. }
  29. }

【参考】

Unity - Manual: Unity architecture (unity3d.com)

深入Unity序列化 - 知乎 (zhihu.com)

关于序列化Serializable - 简书 (jianshu.com)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/127590
推荐阅读
相关标签
  

闽ICP备14008679号