Unity-自定義序列化
序列化是將數(shù)據(jù)結構或對象狀態(tài)轉換為 Unity 可存儲并在以后可重構的格式的自動過程。(請參閱有關腳本序列化的文檔以了解關于 Unity 序列化的更多信息。)
有時可能希望序列化 Unity 的序列化程序不支持的內容。在許多情況下,最好的辦法是使用序列化回調。(請參閱 Unity 的腳本 API 參考:ISerializationCallbackReceiver,了解關于使用序列化回調進行自定義序列化的更多信息。)
序列化回調可讓您在序列化程序從字段中讀取數(shù)據(jù)之前以及在完成對字段的寫入之后收到通知。使用序列化回調可在運行時為難以序列化的數(shù)據(jù)賦予不同于實際序列化時的表示形式。
為實現(xiàn)此目的,請在 Unity 要序列化數(shù)據(jù)之前將數(shù)據(jù)轉換為 Unity 理解的表示形式。然后,在 Unity 將數(shù)據(jù)寫入字段之后,可將序列化的形式轉換回在運行時需要的數(shù)據(jù)形式。
例如:您需要一個樹數(shù)據(jù)結構。如果讓 Unity 直接序列化該數(shù)據(jù)結構,“不支持 null”限制將導致數(shù)據(jù)流變得非常大,從而在許多系統(tǒng)中引起性能下降。下面的示例 1 中顯示了這一情況。
示例 1:Unity 的直接序列化,導致性能問題
using UnityEngine;?
using System.Collections.Generic;?
using System;?
public class VerySlowBehaviourDoNotDoThis : MonoBehaviour {?
[Serializable]?
public class Node {
public string interestingValue = "value"; ?
?//下面的字段使序列化數(shù)據(jù)變得巨大
//因為它引入了"類周期"
public List<Node> children = new List<Node>();?
} ? ?
//這將經(jīng)過序列化 ? ?
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 (); ? ?} }
相反,您告訴 Unity 不要直接序列化樹,并創(chuàng)建單獨的字段來以序列化的格式(適用于 Unity 序列化程序)存儲樹。下面的示例 2 中顯示了這一情況。
示例 2:避免 Unity 直接序列化,并避免性能問題
using System.Collections.Generic;
using System;
using UnityEngine;
public class BehaviourWithTree : MonoBehaviour
{
? ? // 在運行時使用的 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;
? ? }
? ? //用于運行時樹表示的根節(jié)點。不序列化。
? ? Node root = new Node();
? ? //這是我們提供給 Unity 進行序列化的字段。
? ? public List<SerializableNode> serializedNodes;
? ? public void OnBeforeSerialize()
? ? {
? ? ? ? //Unity 即將讀取 serializedNodes 字段的內容。
? ? ? ? // 現(xiàn)在必須"及時"將正確的數(shù)據(jù)寫入該字段。
? ? ? ? if (serializedNodes == null) serializedNodes = new List<SerializableNode>();
? ? ? ? if (root == null) root = new Node();
? ? ? ? serializedNodes.Clear();
? ? ? ? AddNodeToSerializedNodes(root);
? ? ? ? // 現(xiàn)在 Unity 可自由地序列化這個字段,我們應該在稍后反序列化時
? ? ? ? // 找回預期的數(shù)據(jù)。
? ? }
? ? 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 剛剛將新數(shù)據(jù)寫入 serializedNodes 字段。
? ? ? ? //讓我們用這些新值填充我們的實際運行時數(shù)據(jù)。
? ? ? ? if (serializedNodes.Count > 0)
? ? ? ? {
? ? ? ? ? ? ReadNodeFromSerializedNodes(0, out root);
? ? ? ? }
? ? ? ? else
? ? ? ? ? ? root = new Node();
? ? }
? ? int ReadNodeFromSerializedNodes(int index, out Node node)
? ? {
? ? ? ? var serializedNode = serializedNodes[index];
? ? ? ? //將反序列化的數(shù)據(jù)傳輸?shù)絻炔?Node 類
? ? ? ? Node newNode = new Node()
? ? ? ? {
? ? ? ? ? ? interestingValue = serializedNode.interestingValue,
? ? ? ? ? ? children = new List<Node>()
? ? ? ? }
? ? ? ? ;
? ? ? ? // 以深度優(yōu)先的方式讀取樹,因為這正是我們寫入樹的方式。
? ? ? ? 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 視圖中繪制出節(jié)點樹,其中包含用于添加新節(jié)點作為子項的按鈕。
? ? void OnGUI()
? ? {
? ? ? ? if (root != null)
? ? ? ? ? ? Display(root);
? ? }
? ? void Display(Node node)
? ? {
? ? ? ? GUILayout.Label("Value: ");
? ? ? ? // 允許修改節(jié)點的"有用值"。
? ? ? ? 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();
? ? }
}