シリアル化 (シリアライゼーション) は、データ構造やゲームオブジェクトの状態を Unity が保存して後で再構築できる形式に変換する自動処理です。
Unity プロジェクトのデータを整理する方法は、Unity がそのデータをシリアライズする方法に影響し、プロジェクトのパフォーマンスに重大なインパクトを与える可能性があります。ここでは、Unity のシリアル化のための概要と、プロジェクトの最適化方法について説明します。
このセクションでは、以下のトピックについて説明します。
Unity のシリアライザーは、特にランタイムに効率的に動作するように設計されています。このため、Unity でのシリアル化は他のプログラミング環境でのシリアル化とは挙動が異なります。Unity のシリアライザーは、C# のクラスのプロパティではなく、フィールド に直接作用します。そのため、フィールドがシリアル化されるために準拠しなければならない規則があります。次のセクションでは、Unity でフィールドシリアライゼーションを使用するための概要を説明します。
フィールドシリアライゼーションを使用するには、フィールドが以下の状態であることを確認します。
List<T>
であること。ノート: Unity は多階層型 (多次元配列、ジャグ配列、辞書、ネストしたコンテナ型) のシリアライゼーションをサポートしていません。これらをシリアライズする場合、2 つのオプションがあります。
Unity がカスタムクラスをシリアライズするためには、クラスが以下の状態であることが必要です。
UnityEngine.Object
から派生したクラスのインスタンスをフィールドに割り当てそのフィールドを保存する場合、Unity はフィールドをシリアライズしてそのインスタンスへの参照にします。Unity はそのインスタンス自体を個々にシリアライズします。そのため、インスタンスに複数のフィールドが割り当てられても重複しません。しかし、UnityEngine.Object
から派生しないカスタムクラスの場合、Unity はインスタンスの状態を、それらを参照する MonoBehaviour や ScriptableObject のシリアル化されたデータに直接加えます。inline と SerializeReference
の 2 つの方法があります。
SerializeReference
を指定しない場合、Unity はカスタムクラスを値によってインラインでシリアライズします。つまり、カスタムクラスのインスタンスへの参照を複数の異なるフィールドに保存する場合、これらはシリアライズされると別々のオブジェクトになります。それらはシリアライズされると別々のオブジェクトになります。そして、Unity がフィールドをデシリアライズすると、それらは同一のデータを持つ異なる別個のオブジェクトを含みます。SerializeReference
シリアライゼーション: SerializeReference
を指定する場合、Unity はオブジェクトをマネージ参照として確立します。ホストオブジェクトは、オブジェクトをそのシリアル化されたデータに直接保存しますが、専用のレジストリセクションに格納します。SerializeReference
は若干のオーバーヘッドを加えますが、以下のケースをサポートします。
SerializeReference
を使用せずに、カスタムクラスのインスタンスへの参照を複数の異なるフィールドに格納すると、シリアライズ時にそれらは別々のオブジェクトになります。SerializeReference
なしに、Unity は親クラスに属するフィールドのみをシリアライズします。Unity がクラスインスタンスをデシリアライズするとき、派生クラスではなく、親クラスをインスタンス化します。ノート: インラインシリアライゼーションはより効率的なので、SerializeReference
がサポートする機能の 1 つを特に必要としない限り、インラインシリアライゼーションを使用すべきです。SerializeReference
の使用方法の詳細については、SerializeReference のドキュメントを参照してください。
Unity では、以下のような場合を除き、通常プロパティをシリアル化することはありません。
public int MyInt
{
get => m_backing;
private set => m_backing = value;
}
[SerializeField] private int m_backing;
public int MyInt { get; set; }
Unity のシリアライザーがサポートしないもの (例えば、C# の Dictionary) をシリアル化したい場合があるかもしれません。最良の方法は、ISerializationCallbackReceiver インターフェースをクラスで実装することです。これにより、シリアライゼーションとデシリアライゼーション中の重要なポイントで呼び出されるコールバックを実装することができます。
OnBeforeSerialize()
コールバックを呼び出します。このコールバックの内部で、データを Unity が理解できるものに変換することができます。例えば、C# の Dictionary をシリアライズする場合、データを Dictionary からキーの配列と値の配列にコピーします。OnBeforeSerialize()
コールバックが完了した後、Unity は配列をシリアライズします。OnAfterDeserialize()
コールバックを呼び出します。このコールバックの内部で、データをメモリのオブジェクトに都合の良い形に変換して戻すことができます。例えば、キーと値の配列を使用して、C# Dictionary を再入力します。Unity はシリアル化を使って シーン、アセット、アセットバンドル をデバイスのメモリに (または、メモリから) ロードして保存します。これには、独自のスクリプティング API オブジェクトに格納される MonoBehaviour コンポーネントや ScriptableObject などのデータも含まれます。
Unity エディターの機能の多くは、基軸となるシリアル化システム上に構築されています。シリアル化で特に気を付けるべき 2 つの点は インスペクターウインドウ とホットリロードです。
Inspector ウィンドウには、検査されたオブジェクトのシリアライズされたフィールドの値が表示されます。Inspector 内で値を変更すると、Inspector はシリアライズされたデータを更新し、デシリアライゼーションを発生させて検査されたオブジェクトを更新します。
Unity のビルトインオブジェクトも、MonoBehaviour から派生したクラスなどのスクリプトオブジェクトも同様です。
Unity は、Inspector ウィンドウで値を表示したり変更したりする際に、C# のプロパティゲッターやセッターを一切呼び出しません。代わりにシリアライズされたバッキングフィールドに直接アクセスします。
ホットリロードとは、エディターを開いている間にスクリプトを作成または編集し、そのスクリプトの動作をすぐに適用することです。変更を反映させるためにエディターを再起動する必要はありません。
スクリプトを変更して保存すると、Unity はその時点でロードされているすべてのスクリプトデータをホットリロードします。Unity はロードされたすべてのスクリプトにシリアライズ可能な変数を保存し、それらのスクリプトをリロードしてシリアライズ変数を復元します。ホットリロードでは、シリアライズ可能でないデータはすべて破棄されます。そのため、その後データにアクセスすることはできません。
これは、プロジェクト内のすべてのエディターウィンドウとすべての MonoBehaviour に影響します。シリアル化の他のケースとは異なり、private のフィールドは、 ‘SerializeField’ 属性を持たなくても、リロード時にデフォルトでシリアル化されます。
Unity は以下のようにスクリプトを再ロードします。
[SerializeField]
属性がなくても、シリアライゼーションの要件を満たすすべての変数 (private 変数を含む) を復元します。例えば、スクリプトから再ロードした後に参照を NULL にしたい場合など、Unity が private 変数をリストアしないようにする必要がある場合があります。この場合は、[field: NonSerialized]
属性を使用します。シリアル化では、プレハブ は 1 つまたは複数の ゲームオブジェクト または コンポーネント のシリアル化されたデータです。プレハブインスタンスには、プレハブソースとその変更のリストの両方への参照が含まれています。変更は、その特定のプレハブインスタンスを作成するために、Unity がプレハブソースに行う必要があるものです。
プレハブインスタンスは、Unity エディターでプロジェクトを編集している間だけ存在します。Unity エディターは、プレハブソースとプレハブインスタンスの変更の 2 つのシリアル化のデータからゲームオブジェクトをインスタンス化します。
シーンに存在するもの (プレハブやゲームオブジェクトなど) 上でInstantiate
を呼び出すと、以下が行われます。
UnityEngine.Object
から派生するものすべてをシリアル化することができます。UnityEngine.Objects
を参照しているかを連絡します。すべての参照された UnityEngine.Objects
を確認し、それらが Unity がインスタンス化したデータの一部であるかどうかを確認します。参照がテクスチャのような外部のものを指している場合は、その参照をそのまま維持します。参照が子ゲームオブジェクトのような内部のものを指している場合には、Unity は、対応するコピーへの参照をパッチします。EditorUtility.UnloadUnusedAssetsImmediate
は、ネイティブの Unity ガベージコレクターで、標準の C# ガベージコレクターとは異なる目的をもっています。シーンをロードした後に実行され、参照されなくなったオブジェクト (テクスチャなど) を確認して安全にアンロードします。ネイティブ Unity ガベージコレクターは、オブジェクトが外部の UnityEngine.Objects
へのすべての参照を報告するバリエーションでシリアライザーを実行します。このようにして、1 つのシーンで使用されたテクスチャが、次のシーンでガベージコレクターによってアンロードされます。
シリアライゼーションのほとんどはエディターで行われ、デシリアライズはランタイムに集中して行われます。Unity は一部の機能をエディターでのみシリアライズし、他の機能をエディターとランタイムの両方でシリアライズすることができます。
機能 | エディター | ランタイム |
---|---|---|
バイナリ形式のアセット | 読み込み/書き込み対応 | 読み込み対応 |
YAML 形式のアセット | 読み込み/書き込み対応 | サポートなし |
シーン、プレハブ、その他のアセットの保存 | 再生モード以外でサポート | サポートなし |
JsonUtility による個々のオブジェクトのシリアル化 | JsonUtility による読み込み/書き込み対応。 EditorJsonUtility による追加のオブジェクト型へ対応 |
JsonUtility による読み込み/書き込み対応 |
SerializeReference | サポートあり | サポートあり |
ISerializationCallbackReceiver | サポートあり | サポートあり |
FormerlySerializedAs | サポートあり | サポートなし |
オブジェクトは、UNITY_EDITOR スクリプティングシンボル 内でフィールドを宣言するときのように、エディターだけがシリアライズする追加フィールドを持つことができます。
public class SerializeRules : MonoBehaviour
{
# if UNITY_EDITOR
public int m_intEditorOnly;
# endif
}
上記の例では、m_intEditorOnly
フィールドは、エディターでのみシリアライズされ、ビルドには含まれません。これにより、エディターだけで必要なデータをビルドから省くことで、メモリを節約することができます。そのフィールドを使用するコードもまた、条件付きで (例えば #if UNITY_EDITOR ブロックの中で) コンパイルする必要があります。そのようにすると、ビルド時にクラスをコンパイルできます。
エディターは、Unity がランタイムにのみシリアライズするフィールドを持つオブジェクトをサポートしていません (例えば、UNITY_STANDALONE ディレクティブ内でフィールドを宣言する場合など)。
スクリプトのシリアル化でエラーが発生することがあります。これらのうちのいくつかに対する修正を以下に示します。
MonoBehaviour のコンストラクターやフィールドイニシャライザー内で GameObject.Find
などのスクリプティング API を呼び出すと、このエラーが発生します。
これを修正するには、コンストラクターではなく MonoBehaviour.Start
内でスクリプティング API を呼び出します。
System.Serializable
でマークされたクラスのコンストラクター内から GameObject.Find などのスクリプティング API を呼び出すと、このエラーが発生します。
これを修正するには、シリアル化されたオブジェクトに対して、スクリプティング API の呼び出しが、コンストラクターで一切行われないようにコードを変更します。
上の制限は、スクリプティング API の大部分に影響します。ただし、Unity のスクリプティング API の一部だけは例外で、どこからでも呼び出すことができます。
Debug.Log
Mathf
関数Vector3
や Quaternion
のような数学構造体などシリアライズ時のエラーのリスクを減らすため、代替手段がない場合を除き、Unity自体のデータを取得設定する必要がな中のエラー発生のリスクを減らすために、自己完結型で、Unity からデータを取得または設定する必要がない API メソッドのみを呼び出してください。他に選択肢がない場合にのみこれらを呼び出すようにしてください。い自己完結型のAPIメソッドのみを呼び出すようにしてください。
Unity のシリアライゼーションを最適な方法で使用するために、データを整理します。
UnityEngine.Object
から派生するクラスを使用することです。これらのクラスは完全に分かれていて、互いを参照するだけで、コンテンツを埋め込むことはありません。Serialization