Сериализация — это автоматический процесс преобразования структур данных или состояний объектов в формат, который Unity может сохранять и реконструировать позже. (Дополнительную информацию о сериализации Unity см. в документации по Сериализации сценариев.)
Иногда вам может понадобиться сериализовать то, что не поддерживает сериализатор Unity. Во многих случаях лучшим подходом является использование обратных вызовов сериализации. (Дополнительную информацию о пользовательской сериализации с использованием обратных вызовов сериализации см. в Справочнике API сценариев Unity: ISerializationCallbackReceiver.)
Обратные вызовы сериализации позволяют получать уведомления до того, как сериализатор прочитает данные из ваших полей и после того, как он закончит запись в них. Вы можете использовать обратные вызовы сериализации, чтобы предоставить трудно сериализуемым данным представление во время выполнения, отличное от их представления при фактической сериализации.
Для этого преобразуйте свои данные во что-то понятное Unity прямо перед тем, как Unity захочет их сериализовать. Затем, сразу после того, как Unity запишет данные в ваши поля, вы можете преобразовать сериализованную форму обратно в форму, в которой вы хотите хранить данные во время выполнения
Например: вы хотите иметь древовидную структуру данных. Если вы позволите Unity напрямую сериализовать структуру данных, ограничение «нет поддержки нулевого значения» приведет к тому, что ваш поток данных станет очень большим, что приведет к снижению производительности во многих системах. Это показано в примере 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 children = new List();
}
//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 children = new List();
}
// 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 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();
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 ()
}
;
// 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 ();
}
}