【Unity】SerializeReference の挙動検証

概要

SerializeReference を使用することで,
添付画像のようにリストの全ての要素で
Inspectorから設定できる値を変えたりすることも可能です。

コンポーネントソースコードは以下です。

class Sample : MonoBehaviour
{
    [SerializeReference]
    List<ISample> _list = new () { new DataA(), new DataB(), new DataC(), };
}

interface ISample { }
[Serializable]
class DataA : ISample { public int Int; }
[Serializable]
class DataB : ISample { public string Str; }
[Serializable]
class DataC : ISample { public UnityEngine.Object Obj; }

ポリモーフィズムを使用できたり, null を表現できたりと,
SerializeReference に可能性を感じたので色々検証してみます.

検証

Unityのバージョン 2022.3.10f1

YAMLを見てみる

MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 1887110794}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 20af99b3d8d8d714f8528c69a46ced5e, type: 3}
  m_Name: 
  m_EditorClassIdentifier: 
  _list:
  - rid: 4036733945655066624
  - rid: 4036733945655066625
  - rid: 4036733945655066626
  references:
    version: 2
    RefIds:
    - rid: 4036733945655066624
      type: {class: DataA, ns: , asm: Assembly-CSharp}
      data:
        Int: 1
    - rid: 4036733945655066625
      type: {class: DataB, ns: , asm: Assembly-CSharp}
      data:
        Str: Str
    - rid: 4036733945655066626
      type: {class: DataC, ns: , asm: Assembly-CSharp}
      data:
        Obj: {fileID: 0}

YAML を読んでみると, 配列が rid のみを持ち,
配列の要素のインスタンスは以下のように,
rid, class名, namespace名, assembly名 と実データを持っていることがわかります。
class名や namespace名などの変更は気軽にできる分, 気を付けた方がよさそうですね。

    - rid: 4036733945655066624
      type: {class: DataA, ns: , asm: Assembly-CSharp}
      data:
        Int: 1

配列の順番を変更する

2つ目の要素と3つ目の要素を入れ替えてみました。
_list: 以下の rid: の順番が変わるのみのようです。

   m_EditorClassIdentifier:
   _list:
   - rid: 4036733945655066624
-  - rid: 4036733945655066625
   - rid: 4036733945655066626
+  - rid: 4036733945655066625
   references:
     version: 2
     RefIds:

配列の要素を削除する


右クリック→ 「Delete Array Element」で
削除してみたところ, 以下のエラーログが出力されました。

ObjectDisposedException: SerializedProperty _list.Array.data[2] has disappeared!
UnityEditor.SerializedProperty.get_hashCodeForPropertyPath () (at <fe7039efe678478d9c83e73bc6a6566d>:0)
UnityEditor.UIElements.Bindings.SerializedObjectBindingContext+TrackedValues.Remove (UnityEditor.SerializedProperty prop, System.Object cookie) (at <c91a25f185b743118a39aafa100dff09>:0)

保存して diff を見てみると,
リスト内のidとインスタンスのデータが消えていました。

   m_EditorClassIdentifier:
   _list:
   - rid: 4036733945655066624
-  - rid: 4036733945655066626
   - rid: 4036733945655066625
   references:
     version: 2
@@ -368,10 +367,6 @@ MonoBehaviour:
       type: {class: DataB, ns: , asm: Assembly-CSharp}
       data:
         Str: Str
-    - rid: 4036733945655066626
-      type: {class: DataC, ns: , asm: Assembly-CSharp}
-      data:
-        Obj: {fileID: 0}
 --- !u!1660057539 &9223372036854775807
 SceneRoots:
   m_ObjectHideFlags: 0

要素はcsファイルで記述していたのですが,
一応削除することはできるようでしたが,
ゲームを再生してみたところ Null エラーが出力されました。

NullReferenceException: SerializedObject of SerializedProperty has been Disposed.
UnityEditor.SerializedProperty.get_hashCodeForPropertyPath () (at <fe7039efe678478d9c83e73bc6a6566d>:0)

Reset したら要素が復活しました。

シリアライズされているクラスを削除する

以下のようにシリアライズされているクラスの定義を削除してみます。
DataB を削除しました。

 class Sample : MonoBehaviour
 {
     [SerializeReference]
-    List<ISample> _list = new () { new DataA(), new DataB(), new DataC(), };
+    List<ISample> _list = new () { new DataA(), new DataC(), };
 }

 interface ISample { }
 [Serializable]
 class DataA : ISample { public int Int; }
 [Serializable]
-class DataB : ISample { public string Str; }
-[Serializable]
 class DataC : ISample { public UnityEngine.Object Obj; }

コンパイルが終わったタイミングで以下のWarning Logが出力されました。

Missing types referenced from component Sample on game object Scripts:
    DataB, Assembly-CSharp (1 object)

また, SerializeReference が Missing した旨がコンポーネントに表示されました。

git の diff も確認してみましたが, cs ファイルのみでした。

削除したクラスを復元する

先ほど削除した cs ファイルの差分を元に戻してみます。

 class Sample : MonoBehaviour
 {
     [SerializeReference]
-    List<ISample> _list = new () { new DataA(), new DataC(), };
+    List<ISample> _list = new () { new DataA(), new DataB(), new DataC(), };
 }

 interface ISample { }
 [Serializable]
 class DataA : ISample { public int Int; }
 [Serializable]
+class DataB : ISample { public string Str; }
+[Serializable]
 class DataC : ISample { public UnityEngine.Object Obj; }

コンポーネントに表示されていた警告が消え, 配列の要素が元に戻っていました。
値も消す前のままです。

クラスのリネームをしてみる

以下 diff のように class C を class D にリネームしてみました。

 class Sample : MonoBehaviour
 {
     [SerializeReference]
-    List<ISample> _list = new () { new DataA(), new DataB(), new DataC(), };
+    List<ISample> _list = new () { new DataA(), new DataB(), new DataD(), };
 }

 interface ISample { }
@@ -15,4 +15,4 @@ class DataA : ISample { public int Int; }
 [Serializable]
 class DataB : ISample { public string Str; }
 [Serializable]
-class DataC : ISample { public UnityEngine.Object Obj; }
+class DataD : ISample { public UnityEngine.Object Obj; }

コンパイルしたところで, 以下のワーニングが出力されました。

Missing types referenced from component Sample on game object Scripts:
    DataC, Assembly-CSharp (1 object)

コンポーネントにも警告が表示されていました。

また, クラス名をリネーム前の状態に戻してコンパイルすると,
コンポーネントの警告が消え, 元の状態に戻りました。

リネーム用に MovedFrom という属性が用意されているようですね。 forum.unity.com

リファレンス

Unity - Scripting API: SerializeReference