シリアル化は、データ構造やオブジェクトの状態を Unity が保存して後で再構成できる形式に変換する自動プロセスです。 Unityのビルトイン機能の中には、シリアル化を使用するものがあります。保存とロード、インスペクターウィンドウ、インスタンス化、プレハブなどの機能が含まれます。これらのすべての詳細については、ビルトインシリアル化の使用 を参照してください。
Unity プロジェクトのデータを整理する方法は、Unity がそのデータをシリアライズする方法に影響し、プロジェクトのパフォーマンスに重大なインパクトを与える可能性があります。ここでは、Unity のシリアル化のための概要と、プロジェクトの最適化方法について説明します。
シリアル化エラー、カスタムのシリアル化、ビルトインのシリアル化 に関するドキュメントも参照してください。
ホットリロードは、エディターが開いている間にスクリプトを作成または編集し、スクリプトの動作を即座に適用する処理です。変更を有効にするために、アプリケーションやエディターを再起動する必要はありません。
スクリプトを変更して保存すると、Unity は現在ロードされているすべてのスクリプトデータをホットリロードします。まず、ロードしたすべてのスクリプトにシリアライズ可能な変数すべてを格納し、スクリプトをロードした後にそれらを復元します。 シリアライズ可能でないすべてのデータは、スクリプトのリロード後に失われます。
Unity はシリアル化を使って シーン、アセット、アセットバンドル をコンピューターのハードドライブに/からロードして保存します。これには、独自のスクリプティング API オブジェクトに格納される MonoBehaviour コンポーネントや スクリプタブルオブジェクト などのデータも含まれます。
Unity エディターの機能の多くは、基軸となるシリアル化システム上に構築されています。シリアル化で特に気を付けるべき 2 つの点は インスペクターウインドウ とホットリロードです。
インスペクターウィンドウ でゲームオブジェクトのコンポーネントのフィールドの値を表示/変更するときに、Unity はこのデータをシリアライズしてからインスペクターウィンドウに表示します。フィールドの値を表示しているときは、インスペクターウィンドウは Unity のスクリプティング API と通信しません。
スクリプトのプロパティを利用する場合、インスペクターウィンドウの値の表示や変更を行っても、プロパティのゲッターやセッターはまったく呼び出されません。なぜなら、Unity は直接インスペクターウィンドウのフィールドをシリアライズするからです。つまり、インスペクターウィンドウのフィールドの値はスクリプトのプロパティを表示しますが、インスペクターウィンドウの値を変更しても、スクリプト内でプロパティのゲッターとセッターは呼び出されません。
Unity のシリアライザーはリアルタイムのゲーム環境で動作します。これはパフォーマンスに大きな影響を与えます。そのため、Unity でのシリアル化は、他のプログラミング環境ではシリアル化の動作が異なります。以下のセクションでは、Unity でシリアル化を行う方法の概要を説明します。フィールドのシリアル化を使用するには、以下の状態であることを確認する必要があります。
public
または SerializeField 属性をもっていること。static
でないこと。const
でないこと。readonly
でないこと。fieldtype
であること (後述の シリアライズ可能な簡易なフィールドタイプ を参照)Serializable
属性をもつカスタムの非抽象クラスと非ジェネリッククラスSerializable
属性をもつカスタム構造体int
、float
、double
、bool
、string
など)Vector2
、Vector3
、Vector4
、Rect
、Quaternion
、Matrix4x4
、Color
、Color32
、LayerMask
、AnimationCurve
、Gradient
、RectOffset
、GUIStyle
List<T>
ノート: Unity は、マルチレベルタイプ (多次元配列、ジャグ配列、ネストされたコンテナータイプ) のシリアライズをサポートしていません。
これらをシリアライズするには 2 つのオプションがあります。クラスまたは構造体でネストされた型をラップするか、シリアル化コールバック ISerializationCallbackReceiver を使用してカスタム製のシリアル化を実行します。 詳細については、 カスタムシリアル化 に関するドキュメントを参照してください。
以下を確認します。
カスタムのクラスか構造体のフィールドがシリアライズされていることを確認するには、前述の スクリプトでフィールドがシリアライズされているのを確認する方法 を参照してください。
UnityEngine.Object から派生していないカスタムクラスでは、Unity は構造体をシリアライズするのと同じように、値によってインラインでシリアライズします。カスタムクラスのインスタンスへの参照をいくつかの異なるフィールドに格納すると、それらはシリアライズされるときに別々のオブジェクトになります。その後、Unity がそれらのフィールドをデシリアライズすると、フィールドには同じデータを持つ別個のオブジェクトが含まれます。
参照を使用して複雑なオブジェクトグラフをシリアライズする必要がある場合は、Unity がオブジェクトを自動的にシリアライズしないようにしてください。代わりに、 ISerializationCallbackReceiver
を使用して手動でシリアライズします。これにより、Unity がオブジェクト参照から複数のオブジェクトを作成するのを防ぎます。詳細については、ISerializationCallbackReceiver に関するドキュメントを参照してください。
これは、カスタムクラスの場合にのみ当てはまります。Unity は、カスタムクラスを「インライン」でシリアライズします。なぜなら、それらのデータは、使用される MonoBehaviour または ScriptableObject の完全なシリアル化データの一部となるためです。フィールドが public Camera myCamera
などの UnityEngine.Object の派生のクラスを参照するとき、Unity はカメラ UnityEngine.Object
への実際の参照をシリアライズします。 MonoBehaviour
または ScriptableObject
(両方ともUnityEngine.Object
から派生) から派生したスクリプトのインスタンスでも同じことが起こります。
null
はサポートされません以下のスクリプトを使用する MonoBehaviour
をデシリアライズするとき、いくつのアロケーションが発生するか考えてみてください。
class Test : MonoBehaviour
{
public Trouble t;
}
[Serializable]
class Trouble
{
public Trouble t1;
public Trouble t2;
public Trouble t3;
}
1 回アロケーションが発生するのは普通です。Test
オブジェクトで発生します。2 回アロケーションが発生するのも異常ではありません。Test
オブジェクトと Trouble
オブジェクトで発生します。
しかし、Unity は実際には 1000 以上のアロケーションを行います。 シリアライザーは null をサポートしていません。 オブジェクトをシリアライズし、フィールドが null の場合、Unity はその型の新しいオブジェクトをインスタンス化し、それをシリアライズします。 明らかに、これは無限のサイクルにつながる可能性があるため、7 レベルの深度制限があります。 この時点に達すると、Unity は、カスタムクラス、構造体、リスト、配列の型を持つフィールドのシリアライズを停止します。
Unity のサブシステムの多くはシリアル化のシステム上で構築されるため、Test MonoBehaviour
の予想外に大きなシリアル化のストリームは、これらすべてのサブシステムのパフォーマンスを必要以上に低下させます。
public Animal[] animals
に Dog
、Cat
、Giraffe
のインスタンスを加えると、シリアル化後に 3 つの Animal
のインスタンスができます。
この制限に対処する 1 つの方法は、インラインでシリアル化されるカスタムクラスにのみ適用されることに注意することです。他の UnityEngine.Objects
への参照は実際の参照としてシリアル化され、それらのためにポリモーフィズムが実際に機能します。 ScriptableObject
派生クラス、または別の MonoBehaviour
派生クラスを作成し、それを参照します。 これの欠点は、Monobehaviour
またはスクリプタブルオブジェクトをどこかに格納する必要があり、インラインで効率的にシリアライズできないことです。
これらの制限の理由は、シリアル化システムの中核基盤の 1 つとして、オブジェクトのデータストリームのレイアウトが事前にわかっていることです。 つまり、フィールド内に格納されるものではなく、クラスのフィールドの型に依存します。
Unity のシリアル化を最適な方法で使用するために、データを整理します。
Unity に最小限のデータセットをシリアライズさせるためにデータを整理します。 これの主な目的は、コンピューターのハードドライブ上の領域を節約するためではなく、以前のバージョンのプロジェクトとの下位互換性を確実に維持するためです。 シリアライズされたデータの大きなセットを扱う場合、下位互換性は開発の後になるほど難しくなります。
Unity が複製されたデータやキャッシュされたデータをシリアライズしないようにデータを整理します。 これは、下位互換性にとって重大な問題が起きる原因になります。データが同期されにくくなるため、エラーが発生する可能性が高くなります。
他のクラスを参照するネストされた再帰的な構造を避けるようにします。シリアライズされた構造のレイアウトは常に同じである必要があります。 つまり、データとは独立し、スクリプト内に公開されているもののみに依存します。他のオブジェクトを参照する唯一の方法は、UnityEngine.Object から派生したクラスを使用することです。これらのクラスは完全に分かれていて、互いを参照するだけで、コンテンツを埋め込むことはありません。
スクリプトをリロードすると、Unity はロードしたすべてのスクリプトのすべての変数をシリアライズして保存します。 スクリプトをリロードした後、Unity はそれらをシリアル化前の元の値に復元します。
スクリプトをリロードすると、Unity は変数に SerializeField 属性がなくても、シリアライゼーションの要件を満たすすべての変数 (プライベート変数を含む) を復元します。特に、プライベート変数がリストアされることを避けることが必要な場合もあります。例えば、スクリプトからリロードした後に参照を null にする場合などです。 この場合、 NonSerializable 属性を使用します。
Unity は決して静的変数を復元しないので、スクリプトをリロードした後に保持する必要のある状態には静的変数を使用しないでください。