Unityのシリアライズについてのまとめ
from 作業メモ CGEに準拠したAnimator Controllerの生成
スクリプトのシリアル化
Unityはオブジェクトのインスタンスをアセットとして保存する際、publicフィールドやSerializeFiled属性を持ったフィールドの値をシリアライズする
シリアライズがUnityでどのように利用されているかは ビルトインのシリアル化 を参照
ホットリロード時のシリアライズの挙動は、その他の場合のシリアライズの挙動と異なる?
SerializeField属性を適用していないprivateメンバもすべてシリアライズされる
そのため、ホットリロード時にシリアライズしてほしくないフィールドにはNonSerializable属性を適用する必要がある
ホットリロード時に特定の値に初期化されていてほしいフィールドにはNonSerializable属性を適用すべき
値が初期化されていない時はホットリロード時の挙動をまず疑うようにする
また、静的フィールドはシリアライズされないため、ホットリロードの際に初期化されることに注意
むしろリロードしたら初期化される方が健全な気もするが…
ホットリロードが使用されるのはエディタ拡張のスクリプトのみ?
ランタイムのスクリプトがホットリロードされるような状況は存在するか?
インスペクターウインドウの値はシリアライズされてからオブジェクトに渡されるため、インスペクターウインドウの値を変更したタイミングでsetterが呼び出されるわけではない
setterが呼び出されるのは、オブジェクトが逆シリアライズされるタイミング?
publicなフィールドがすべてシリアライズされ、privateなフィールドは属性を適用してシリアライズすることから、opt-inとopt-outの中間といえる?
フィールドの型がシリアライズに対応していない場合はシリアライズできない
UnityEngine.Objectの派生クラスはすべて対応
プリミティブ型とEnum型もすべて対応
Unityビルトイン型は一部が対応
Vector2、Vector3、Vector4、Rect、Quaternion、Matrix4x4、Color、Color32、LayerMask、AnimationCurve、Gradient、RectOffset、GUIStyle
ユーザ定義のクラスや構造体は、Serializable属性が適用されていれば対応
ただし、抽象クラスやジェネリッククラスはSerializable属性を適用してもシリアライズに対応しない
そもそも属性を適用できない?
また、フィールドに抽象、静的、ジェネリックのものを含んでいる場合も対応しない
フィールドがシリアライズできないのではなく、クラスがシリアライズできなくなるのか?
UnityEngine.Objectから派生していないクラスは参照を使用せずにシリアライズされるため、上記を含んでいるとシリアライズできなくなりそう
なぜわざわざSerializable属性を適用する必要があるのか?ユーザ定義のクラスや構造体を(条件を満たしていれば)すべてシリアライズ可能とするのではダメなのか?
フィールドのシリアライズ可能条件は、「フィールドの型が○○であること」という形なので、その条件に照らせば、UnityEngine.Objectから派生していないカスタムクラスやカスタム構造体は一様にシリアライズ不可と判定される
UnityEngine.Objectから派生していないカスタムクラスは、クラス内の値がそのままファイルに書き出され、逆シリアライズ時にその値を使用してインスタンスが生成される
同一のオブジェクトを複数のフィールドで参照する場合は ISerializationCallbackReceiverを使用してシリアライズする
大抵の場合はそのままシリアライズしても問題は発生しなさそうだが、バグの要因の一つとして頭に入れておくのが良いか
シリアライズ可能な型の1次元配列、および1次元リストは対応
多次元配列などは対応しない
コールバックを使用して、シリアライズ可能な型に変換するなどの方法で対応させることは可能
カスタムのシリアル化
シリアライズ時にnullになっているフィールドは、インスタンスを生成(コンストラクタを実行?)してからシリアライズされる
そのため、TroubleクラスがTrouble型のフィールドを持っており、なおかつ、Troubleクラスのインスタンス生成時に当該フィールドがnullとなっている場合は、Troubleクラスのコンストラクタが無限に呼び出される
この現象によるハングアップを防ぐため、コンストラクタの呼び出しが7階層に達した場合はシリアライズが強制的に停止される
エラーメッセージは出るか?
UnityEngine.Objectから派生していないクラスは、シリアライズ時にポリモーフィズムがサポートされない
参照ではなく値が書き出されるため、フィールドにどの型のオブジェクトが格納されているかに依らず、フィールドの型に依存して値が書き出される
互換性を確保するため、シリアライズで書き出される値はなるべく少なくなるようにする
UnityEngine.Objectから派生したクラスは、シリアライズ時に値ではなく参照が使用されるため、そのようなクラスをフィールドに持つようにすれば、実際に書き出される値を少なくすることができる
スクリプトのシリアル化に関連するエラー
UnityのAPIはコンストラクタやフィールド初期化子で呼び出すことはできず、Awake() や Start() で呼び出す必要がある
これってシリアライズに関係する話か…?
ビルトインのシリアル化
シーン、アセット、アセットバンドルの保存と読み込みにはシリアライズが使用されている
シーンはGameObjectと、それにアタッチされたComponentで構成されており、それらのインスタンスがシリアライズされて保存されているといえる
GameObjectとComponentはUnityEngine.Objectの派生クラス
また、シーンの状態もYAML形式にシリアライズされて保存される
テキストシリアル化ファイルの形式
アセットのmetaファイルも同様にYAML形式でシリアライズされる
前述の通り、インスペクターウインドウでの値のやり取りはシリアライザを介して行われている
前述の通り、ホットリロード時のシリアライズは通常のシリアライズと挙動が異なり、privateメンバもすべてシリアライズされる
prefabの仕組みはシリアライズによって実現されている
prefabの構成要素は下記
GameObject
Component
prefabへの変更
おそらく、元のGameObjectとComponentの両方がシリアライズされて保存されており、それらのフィールドに対する変更のリストがシリアライズされて保存されている
変更のリストはYAML形式?
Instantiateはシーン上のオブジェクトのコピーを生成する操作だが、これはインスタンスのクローンによって行われるのではなく、いったんインスタンスをシリアライズして、逆シリアライズすることによって行われる
Unityは一貫してシリアライズを介してインスタンスを生成する仕様になっているのかもしれない
フィールドとなっているUnityEngine.Objectの派生クラスは、値ではなく参照としてシリアライズされるが、Instantiateの際は適切にクローンが生成される
テクスチャなどのファイルを参照している場合はクローンが生成されず、子GameObjectなどを参照している場合はクローンが生成される
Unityではガベージコレクタもシリアライザを利用する
アセットへの参照が存在するか確認する際にシリアライザが実行される
カスタムのシリアル化
通常のシリアライザで適切にシリアライズできない場合、コールバックを使用してシリアライズの挙動を変更する
カスタムのシリアライズが必要な場面の例
1次元配列や1次元リスト以外のコレクションをシリアライズしたいとき
UnityEngine.Objectの派生クラスでないクラスのインスタンスを複数のフィールドから参照しているとき
通常のシリアライズではコンストラクタが無限に呼び出されてしまうとき
#Unity