文档

Unity Engine

Unity Engine

自定义序列化

How to serialize additional items not supported by Unity's serializer.
阅读时间5 分钟最后更新于 1 个月前

如果要序列化 Unity 序列化程序不支持的内容(例如 C# 字典),可以在类中实现 ISerializationCallbackReceiver 接口。这允许您实现 Unity 在序列化和反序列化期间的关键点调用的回调。
使用序列化回调可在运行时为难以序列化的数据提供与序列化时不同的表示形式。您可以在 Unity 要序列化数据之前将数据转换为 Unity 理解的表示形式。在 Unity 将数据写入字段之后,可将序列化的数据转换回在运行时需要的形式。
  1. 当对象即将序列化时,Unity 会调用
    OnBeforeSerialize()
    回调。在此回调中,您可以将数据转换为 Unity 理解的形式。例如,要序列化 C# 字典,请将字典中的数据复制到键数组和值数组中。
  2. OnBeforeSerialize()
    回调完成后,Unity 会序列化数组。
  3. 随后,在反序列化对象时,Unity 会调用
    OnAfterDeserialize()
    回调。在此回调中,可以将数据转换回内存中的对象方便理解的形式。例如,使用键和值数组重新填充 C# 字典。

示例 1:Unity 的默认序列化会导致性能问题

假设您需要一个树数据结构。如果让 Unity 直接序列化该数据结构,“不支持 null”限制将导致数据流变得非常大,从而在许多系统中引起性能下降。
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 (); }}

示例 2:自定义序列化可避免性能问题

在这种情况下,您告诉 Unity 不要直接序列化树,并创建单独的字段来以序列化的格式(适用于 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 (); }}

其他资源