Version: 5.5
スクリプトの制限
UnityEvent

スクリプトシリアライゼーション

Serialization is a central feature in Unity.

In the Unity Engine, serialization is used to load Scenes, Assets, and AssetBundles from disk. This includes data saved in your own script objects such as MonoBehaviour components and ScriptableObjects.

Additionally, many of the features in the Unity Editor build on top of the core serialization system.

シリアライズを利用するビルトイン機能

To give you an understanding of how serialization works in Unity, the following sections provide a list of the built-in features of Unity that use serialization.

MonoBehaviour and ScriptableObject

Data saved in script objects in the form of MonoBehaviour components and ScriptableObjects are saved and loaded using serialization. This happen at runtime when running your game, but also at many other points in time, as is detailed below.

インスペクターウィンドウ

The Inspector window doesn’t communicate with the C# API to know the values of a property that it is inspecting. It asks the object to serialize itself, and then displays this serialized data.

プレハブ

内部的には、プレハブは 1 つ以上のゲームオブジェクトとコンポーネントのシリアライズしたデータストリームです。プレハブインスタンスは、このインスタンスのためにシリアライズされたデータに関する変更リストです。プレハブの概念は、実際には、エディターでプロジェクトを編集している間のみ存在しています。プレハブの変更は Unity がビルドを行う際、通常のシリアライズのストリームの中に返され、オブジェクトがビルドでインスタンス化されるとき、これらのオブジェクトがプレハブになるための参照がありません。

インスタンス化

When you call Instantiate() on anything that exists in a Scene, such as a Prefab or a GameObject, Unity serializes the object. This happens both at runtime and in the Editor. Note that everything that derives from UnityEngine.Object can be serialized.

Unity then creates a new GameObject and deserializes (that is, loads) the data onto the new GameObject. Next, Unity runs the same serialization code in a different variant to report which other UnityEngine.Objects are being referenced. It checks all referenced UnityEngine.Objects to see if they are part of the data being Instantiated(). If the reference points to something “external”, such as a Texture, Unity keeps that reference as it is. If the reference points to something “internal”, such as a child GameObject, Unity patches the reference to the corresponding copy.

保存

If you open a .unity Scene file with a text editor, and have set the Unity Editor to force text serialization, it runs the serializer with a yaml backend. See www.yaml.org for further information.

読み込み

Backwards-compatible loading is built on top of serialization. In-Editor yaml loading, as well as the run-time loading of Scenes, Assets, and AssetBundles, all use the serialization system.

Hot reloading in the Unity Editor

When you change and save a Unity script, Unity serializes all managed representations of objects that derive from UnityEngine.Object. This includes (among other things) all Editor windows, as well as all MonoBehaviours in the project. Unity then destroys all these objects, unloads the old C# code, loads the new C# code, recreates the objects, and then deserializes the data streams of the objects back onto the new objects.

If a hot reload happens while the Editor is in Play mode, all states that are not serializable are lost. Note that unlike other cases of serialization in Unity, private fields are serialized by default when hot reloading, even if they don’t have the ‘SerializeField’ attribute.

Resource.GarbageCollectSharedAssets()

Resource.GarbageCollectSharedAssets() is the native Unity garbage collector. Note that it has a different function to the C# garbage collector. It runs after you load a Scene, to ensure that objects from the previous Scene are no longer referenced, and so can be unloaded. The native Unity garbage collector runs the serializer in a variation in which objects report all references to external UnityEngine.Objects. This is how Textures that were used by Scene1 are unloaded in Scene2.

The serialization system is written in C++. It is used for all internal object types, such as Textures, AnimationClips, and Cameras, among others. Serialization happens at the UnityEngine.Object level. Each UnityEngine.Object is always serialized as a whole. They can contain references to other UnityEngine.Objects, and these references get serialized properly.

Serialization rules

Because of the very high performance requirements that the serializer has, it does not always behave exactly like a C# developer would expect from a serializer. Below is some guidance on how to make best use of serialization.

スクリプトのフィールドがシリアライズされていることを確認する方法

以下を確認します。

  • is public, or has a SerializeField attribute (doesn’t need to be serialized for hot reloading)
  • static ではないこと
  • const ではないこと
  • readonly ではないこと
  • has fieldtype that is of a type that can be serialized (See below.)

シリアライズできるフィールドタイプ

  • Custom non-abstract classes with [Serializable] attribute.
  • Custom structs with [Serializable] attribute (added in Unity 4.5).
  • References to objects that derive from UnityEngine.Object.
  • プリミティブ型(int, float, double, bool, string, etc.)
  • Enum types.
  • Certain Unity built-in types: Vector2, Vector3, Vector4, Rect, Quaternion, Matrix4x4, Color, Color32, LayerMask, AnimationCurve, Gradient, RectOffset, GUIStyle.
  • シリアライズできるフィールドタイプの配列
  • シリアライズできるフィールドタイプの List<T>

シリアライザが期待した動作をしない状況

カスタムのクラスは構造体のように動作します

[Serializable]
class Animal
{
   public string name;
}

class MyScript : MonoBehaviour
{
      public Animal[] animals;
}

If you populate the animals array with three references to a single Animal object, in the serialization stream, you find 3 objects. When it’s deserialized, there are now three different objects. If you need to serialize a complex object graph with references, you cannot rely on Unity’s serializer doing that all automatically for you; you have to do some work to get that object graph serialized yourself. See the example below on how to serialize things Unity doesn’t serialize by itself.

これはカスタムクラスのみの話であるということに注意してください。カスタムクラスのデータは、使用される MonoBehaviour の完全なシリアライズデータの一部となるので、カスタムクラスは「インライン」でシリアライズされます。 public Camera myCamera のような UnityEngine.Object の派生クラスの何かへの参照を持つフィールドがある場合、その Camera からのデータは、インラインでシリアライズされません。その代りに、Camera の UnityEngine.Object への実際の参照がシリアライズされます。

カスタムクラスでは null はサポートされません

以下のスクリプトを使用する MonoBehaviour をデシリアライズするとき、いくつのアロケーションが発生するか考えてみてください。

class Test : MonoBehaviour
{
    public Trouble t;
}

[Serializable]
class Trouble
{
   public Trouble t1;
   public Trouble t2;
   public Trouble t3;
}

It wouldn’t be strange to expect 1 allocation: That of the Test object. It also wouldn’t be strange to expect 2 allocations: One for the Test object and one for a Trouble object.

However, the correct answer is 729. The serializer does not support null. If it serializes an object, and a field is null, Unity instantiates a new object of that type, and serializes that. Obviously this could lead to infinite cycles, so there is a depth limit of 7 levels. At that point Unity stops serializing fields that have types of custom classes, structs, lists, or arrays.

Unity のサブシステムの多くはシリアライゼーションシステム上でビルドするため、Test MonoBehaviour のこの予想外に大きなシリアライゼーションのストリームは、これらすべてのサブシステムの実行速度の低下を招きます。

注意 多くのプロジェクトで重大なパフォーマンスの問題の原因となるため、Unity 4.5 以降警告メッセージを発するようになりました。

ポリモーフィズムはサポートされません

もし、public Animal[] animals に犬、猫、キリンのインスタンスを収納して、シリアライズを行うと、3 つの Animal インスタンスを持つことになります。

この制限に対処する 1 つの方法は、インラインでシリアル化されるカスタムクラスにのみ適用されることに注意することです。他の UnityEngine.Objects への参照は実際の参照としてシリアル化され、それらのためにポリモーフィズムが実際に機能します。 ScriptableObject 派生クラス、または別の MonoBehaviour 派生クラスを作成し、それを参照します。 これの欠点は、Monobehaviour またはスクリプタブルオブジェクトをどこかに格納する必要があり、インラインで効率的にシリアライズできないことです。

The reason for these limitations is that one of the core foundations of the serialization system is that the layout of the datastream for a object is known ahead of time; it depends on the types of the fields of the class, rather than what happens to be stored inside the fields.

Unity のシリアライザがサポートしないものをシリアライズするためには

多くの場合、最善のアプローチはシリアライズのコールバックを使用することです。シリアライザがフィールドからデータを読み込む前や、書き込みが完了した後に通知可能です。シリアライズのコールバックを使用して、実行時にシリアライズするのが難しいデータを別タイプのデータとして処理させたいときに使用できます。

Unity がデータをシリアライズする前に、Unityがシリアライズできる形式に変換します。それから、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 ();
}
}

代わりに、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 ();
}
}

シリアライザから発生するこれらのコールバックも含め、シリアライザは通常、メインスレッド上にはありません。そのため、Unity API の呼び出しに関してできることはとても限られています。ただし、Unity がシリアライズできない形式から Unityがシリアライズできる形式への必要なデータ変換は行えます。

スクリプトのシリアライズエラー

スクリプトがコンストラクターかフィールドイニシアライザーから Unity API を呼び出すとき、または、デシリアライゼーション (読み込み) の間に、エラーがトリガーされます。ここでは、エラーの要因となる良くない例を紹介します。

Unity API のほとんどは、例えば、MonoBehaviour の StartUpdate のようなメインスレッドから呼び出します。

Unity API の一部だけが、Debug.LogMathf などのスクリプトコンストラクターやフィールドイニシアライザーから呼び出されるべきです。その理由は、デシリアライゼーションの間、クラスのインスタンスをコンストラクトするときにはコンストラクターが呼び出され、これはメインスレッド上でのみ実行されるべきなのに、最終的にはメインスレッド以外で実行されるからです。そのため、スクリプトコンストラクターやフィールドイニシアライザーから Unity API すべてを呼び出す場合、エラーが発生します。

Unity API のコンストラクターかフィールドイニシアライザーからの呼び出し

When Unity creates an instance of a MonoBehaviour or ScriptableObject derived class, it calls the default constructor to create the managed object. This happens before entering the main loop, and before the Scene has been fully loaded. Field initializers are also called from the default constructor of a managed object. In general, do not call the Unity API from a constructor, as this is unsafe for the majority of the Unity API.

悪い

//NOTE: THIS IS A BAD EXAMPLE TO DEMONSTRATE POOR PRACTISE  - DO NOT REUSE

public class FieldAPICallBehaviour : MonoBehaviour
{
   public GameObject foo = GameObject.Find("foo");   // This line generates an error 
                        // message as it should not be called from within a constructor

}
//NOTE: THIS IS A BAD EXAMPLE TO DEMONSTRATE POOR PRACTISE - DO NOT REUSE

public class ConstructorAPICallBehaviour : MonoBehaviour
{
   ConstructorAPICallBehaviour()
   {
       GameObject.Find("foo");   // This line generates an error message
                                // as it should not be called from within a constructor
   }
}

Both these cases generate the error message: Find is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call in in Awake or Start instead.

MonoBehaviour.Start で Unity API への呼び出しを行うことにより修正できます。

シリアライズ中に呼び出されるメソッド

When Unity loads a Scene, it recreates the managed objects from the saved Scene and populates them with the saved values (deserializing). In order to create the managed objects, call the default constructor for the objects. If a field referencing a object is saved (serialized) and the object default constructor calls the Unity API, you get an error when loading the Scene. As with the previous error, it is not yet in the main loop and the Scene is not fully loaded. This is considered unsafe for the majority of the Unity API.

悪い

//NOTE: THIS IS A BAD EXAMPLE TO DEMONSTRATE POOR PRACTISE  - DO NOT REUSE

public class SerializationAPICallBehaviour : MonoBehaviour
{
   [System.Serializable]
   public class CallAPI
   {
       public CallAPI()
       {
           GameObject.Find("foo"); // This line generates an error message 
                                                 // as it should not be called during serialization

       }
   }

   CallAPI callAPI;
}

This generates the error: Find is not allowed to be called during serialization, call it from Awake or Start instead.

To fix this, refactor your code so that no Unity API calls are made in any constructors for any serialized objects. If you need to call the Unity API for a object, do this in the main thread from one of the MonoBehaviour callbacks, such as Start, Awake or Update.

スクリプトの制限
UnityEvent