Version: 2020.3
内置序列化
脚本序列化错误

自定义序列化

序列化是将数据结构或对象状态转换为 Unity 可存储并在以后可重构的格式的自动过程。(请参阅有关脚本序列化的文档以了解关于 Unity 序列化的更多信息。)

有时可能希望序列化 Unity 的序列化程序不支持的内容。在许多情况下,最好的办法是使用序列化回调。(请参阅 Unity 的脚本 API 参考:ISerializationCallbackReceiver,了解关于使用序列化回调进行自定义序列化的更多信息。)

序列化回调可让您在序列化程序从字段中读取数据之前以及在完成对字段的写入之后收到通知。使用序列化回调可在运行时为难以序列化的数据赋予不同于实际序列化时的表示形式。

为实现此目的,请在 Unity 要序列化数据之前将数据转换为 Unity 理解的表示形式。然后,在 Unity 将数据写入字段之后,可将序列化的形式转换回在运行时需要的数据形式。

例如:您需要一个树数据结构。如果让 Unity 直接序列化该数据结构,“不支持 null”限制将导致数据流变得非常大,从而在许多系统中引起性能下降。下面的示例 1 中显示了这一情况。

示例 1:Unity 的直接序列化,导致性能问题


using UnityEngine;
using System.Collections.Generic;
using System;

public class VerySlowBehaviourDoNotDoThis : MonoBehaviour {
    [Serializable]
    public class Node {
        public string interestingValue = "value";
        //The field below is what makes the serialization data become huge because
        //it introduces a 'class cycle'.
        public List<Node> children = new List<Node>();
    }
    //this gets serialized
    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 不要直接序列化树,并创建单独的字段来以序列化的格式(适用于 Unity 序列化程序)存储树。下面的示例 2 中显示了这一情况。

示例 2:避免 Unity 直接序列化,并避免性能问题


using System.Collections.Generic;
using System;

public class BehaviourWithTree : MonoBehaviour, ISerializationCallbackReceiver {
    // Node class that is used at runtime.
    // This is internal to the BehaviourWithTree class and is not serialized.
    public class Node {
        public string interestingValue = "value";
        public List<Node> children = new List<Node>();
    }
    // Node class that we will use for serialization.
    [Serializable]
    public struct SerializableNode {
        public string interestingValue;
        public int childCount;
        public int indexOfFirstChild;
    }
    // The root node used for runtime tree representation. Not serialized.
    Node root = new Node();
    // This is the field we give Unity to serialize.
    public List<SerializableNode> serializedNodes;
    public void OnBeforeSerialize() {
        // Unity is about to read the serializedNodes field's contents.
        // The correct data must now be written into that field "just in time".
        if (serializedNodes == null) serializedNodes = new List<SerializableNode>();
        if (root == null) root = new Node ();
        serializedNodes.Clear();
        AddNodeToSerializedNodes(root);
        // Now Unity is free to serialize this field, and we should get back the expected 
        // data when it is deserialized later.
    }
    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 has just written new data into the serializedNodes field.
        //let's populate our actual runtime data with those new values.
        if (serializedNodes.Count > 0) {
            ReadNodeFromSerializedNodes (0, out root);
        } else
        root = new Node ();
    }
    int ReadNodeFromSerializedNodes(int index, out Node node) {
        var serializedNode = serializedNodes [index];
        // Transfer the deserialized data into the internal Node class
        Node newNode = new Node() {
            interestingValue = serializedNode.interestingValue,
            children = new List<Node> ()
        }
        ;
        // The tree needs to be read in depth-first, since that's how we wrote it out.
        for (int i = 0; i != serializedNode.childCount; i++) {
            Node childNode;
            index = ReadNodeFromSerializedNodes (++index, out childNode);
            newNode.children.Add (childNode);
        }
        node = newNode;
        return index;
    }
    // This OnGUI draws out the node tree in the Game View, with buttons to add new nodes as children.
    void OnGUI() {
        if (root != null) {
            Display (root);
        }
    }
    void Display(Node node) {
        GUILayout.Label ("Value: ");
        // Allow modification of the node's "interesting 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 ();
    }
}




• 2017–05–15 页面已发布

内置序列化
脚本序列化错误