シリアル化は、データ構造やオブジェクトの状態を、Unity に保存し、後で再構築できる形式に変換する自動プロセスです。(Unity のシリアル化の詳細については、スクリプトのシリアル化 に関するドキュメントを参照してください。)
Unity がサポートしていない形式のデータをシリアライズしたい場合があるかもしれません。多くの場合、最善の方法はシリアル化コールバックを使用することです。(シリアル化コールバックを使用したカスタムシリアライゼーションの詳細については、Unity のスクリプトリファレンス 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";
//以下のフィールドは、シリアル化データが巨大になる原因です。なぜなら、
// 'class cycle' を導入するからです。
public List<Node> children = new List<Node>();
}
//これはシリアル化されます
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 クラス。
//これは 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;
}
// ランタイムのツリー表現に使用されるルートノード。 シリアル化されていません。
Node root = new Node();
// これは、シリアル化するために Unity に与えるフィールドです
public List<SerializableNode> serializedNodes;
public void OnBeforeSerialize() {
// Unity は serializedNodes フィールドのコンテンツを読み取ろうとしています。
// 正しいデータがピッタリのタイミングでそのフィールドに書き込まれる必要があります。
if (serializedNodes == null) serializedNodes = new List<SerializableNode>();
if (root == null) root = new Node ();
serializedNodes.Clear();
AddNodeToSerializedNodes(root);
// Unity は自由にフィールドをシリアライズできます。後に非シリアル化されるときに、
// 期待されるデータを取り戻します。
}
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 は、serializedNodes フィールドに新しいデータを書き込んだばかりです。
//実際のランタイムデータにこれらの新しい値を入力してみましょう。
if (serializedNodes.Count > 0) {
ReadNodeFromSerializedNodes (0, out root);
} else
root = new Node ();
}
int ReadNodeFromSerializedNodes(int index, out Node node) {
var serializedNode = serializedNodes [index];
//非シリアル化されたデータを内部の Node クラスに転送します
Node newNode = new Node() {
interestingValue = serializedNode.interestingValue,
children = new List<Node> ()
}
;
// ツリーは、最初に深さ優先で読み取る必要があります。なぜなら、それが書き出した方法であるためです。
for (int i = 0; i != serializedNode.childCount; i++) {
Node childNode;
index = ReadNodeFromSerializedNodes (++index, out childNode);
newNode.children.Add (childNode);
}
node = newNode;
return index;
}
// この OnGUI により、ゲームビューにノードツリーが描画され、新しいノードを子として追加するためのボタンが表示されます。
void OnGUI() {
if (root != null) {
Display (root);
}
}
void Display(Node node) {
GUILayout.Label ("Value: ");
// ノードの "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 公開ページ