カスタム製のシリアル化
How to serialize additional items not supported by Unity's serializer.
読み終わるまでの所要時間 7 分最終更新 1ヶ月前
Unity のシリアライザーがサポートしていないもの (例えば C# ディクショナリ) をシリアル化する場合は、クラスに ISerializationCallbackReceiver インターフェースを実装できます。これにより、シリアル化時やデシリアライズ時の重要なポイントで Unity が呼び出すコールバックを実装することができます。
シリアル化コールバックを使用すると、ランタイム時にシリアル化するのが難しいデータを、シリアル化時とは異なる形式にすることができます。Unity がデータをシリアル化する前に、Unity がシリアル化できる形式にデータを変換できます。Unity がデータをフィールドに書き込んだ後、シリアル化されたデータをランタイム時に必要な形式に変換できます。
- オブジェクトがシリアル化されようとするとき、Unity は コールバックを呼び出します。このコールバックバックでは、Unity が理解できる形式にデータを変換することができます。例えば、C# の Dictionary をシリアル化する場合、データを Dictionary からキーの配列と値の配列にコピーします。
OnBeforeSerialize() - コールバックが完了した後、Unity は配列をシリアル化します。
OnBeforeSerialize() - その後、オブジェクトがデシリアライズされようとするとき、Unity は コールバックを呼び出します。このコールバックバックでは、データをメモリ内のオブジェクトにとって都合の良い形式に変換して戻すことができます。例えば、キーと値の配列を使用して、C# Dictionary を再入力します。
OnAfterDeserialize()
例 1Unity のデフォルトのシリアル化によりパフォーマンスの問題が生じる
例えば、データをツリー構造にしたいとします。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 (); }}