スクリプトの制限
ビルトインのシリアライゼーション

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

シリアライゼーションは、データ構造やオブジェクトの状態を 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 属性をもつカスタム構造体

  • UnityEngine.Object から派生するオブジェクトへの参照

  • プリミティブなデータ型 (intfloatdoubleboolstring など)

  • Enum 型

  • 特定の Unity ビルトイン型: Vector2Vector3Vector4RectQuaternionMatrix4x4ColorColor32LayerMaskAnimationCurveGradientRectOffsetGUIStyle

シリアライズ可能なコンテナーフィールドタイプ

  • シリアライズ可能な簡易なフィールドタイプの配列

  • シリアライズ可能な簡易なフィールドタイプの List<T>

注意: Unity は、マルチレベルタイプ (多次元配列、ジャグ配列、ネストされたコンテナータイプ) のシリアライズをサポートしていません。
これらをシリアライズするには 2 つのオプションがあります。クラスまたは構造体でネストされた型をラップするか、シリアライゼーションコールバック ISerializationCallbackReceiver を使用してカスタムシリアライゼーションを実行します。 詳細については、 カスタムシリアライゼーション に関するドキュメントを参照してください。

カスタムクラスがシリアライズ可能なことを確かめる方法

以下を確認します。

  • Serializable 属性をもっていること。

  • フィールドが抽象でないこと。

  • フィールドが静的でないこと。

  • ジェネリッククラスから継承したとしてもフィールドがジェネリックでないこと。

カスタムのクラスか構造体のフィールドがシリアライズされていることを確認するには、前述の スクリプトでフィールドがシリアライズされているのを確認する方法 を参照してください。

###シリアライザーが予期しない挙動を行う場合

カスタムのクラスが構造体のように挙動する

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[] animalsDogCatGiraffe のインスタンスを加えると、シリアライゼーション後に 3 つの Animal のインスタンスができます。

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

これらの制限の理由は、シリアライゼーションシステムの中核基盤の 1 つとして、オブジェクトのデータストリームのレイアウトが事前にわかっていることです。 つまり、フィールド内に格納されるものではなく、クラスのフィールドの型に依存します。

ヒント

シリアライゼーションの最適化

Unity のシリアライゼーションを最適な方法で使用するために、データを整理します。

  • Unity に最小限のデータセットをシリアライズさせるためにデータを整理します。 これの主な目的は、コンピューターのハードドライブ上の領域を節約するためではなく、以前のバージョンのプロジェクトとの下位互換性を確実に維持するためです。 シリアライズされたデータの大きなセットを扱う場合、下位互換性は開発の後になるほど難しくなります。

  • Unity が複製されたデータやキャッシュされたデータをシリアライズしないようにデータを整理します。 これは、下位互換性にとって重大な問題が起きる原因になります。データが同期されにくくなるため、エラーが発生する可能性が高くなります。

  • 他のクラスを参照するネストされた再帰的な構造を避けるようにします。シリアライズされた構造のレイアウトは常に同じである必要があります。 つまり、データとは独立し、スクリプト内に公開されているもののみに依存します。他のオブジェクトを参照する唯一の方法は、UnityEngine.Object から派生したクラスを使用することです。これらのクラスは完全に分かれていて、互いを参照するだけで、コンテンツを埋め込むことはありません。

エディターコードをホットリロード可能にする

スクリプトをリロードすると、Unity はロードしたすべてのスクリプトのすべての変数をシリアライズして保存します。 スクリプトをリロードした後、Unity はそれらをシリアライゼーション前の元の値に復元します。

スクリプトをリロードすると、Unity は変数に SerializeField 属性がなくても、シリアライゼーションの要件を満たすすべての変数 (プライベート変数を含む) を復元します。特に、プライベート変数がリストアされることを避けることが必要な場合もあります。例えば、スクリプトからリロードした後に参照を null にする場合などです。 この場合、 NonSerializable 属性を使用します。

Unity は決して静的変数を復元しないので、スクリプトをリロードした後に保持する必要のある状態には静的変数を使用しないでください。


スクリプトの制限
ビルトインのシリアライゼーション