テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】Genericなクラスをシリアライズで来た

ジェネリックなクラスをシリアライズ

 Unity 2020.1aを試している時に気づいたのですが、どうやらジェネリックなクラスをシリアライズ可能になったみたいです。記憶だとUnity 2019.3だったような気がしましたが、改めて手元で確認するとUnity 2020.1ですので、多分勘違いです。

 今までのUnityではジェネリックなクラスを直接シリアライズすることは出来ませんでした。そのため、一旦ジェネリックなクラスを継承したクラスを定義するという面倒な実装が必要でした。例えば任意の引数を要求するUnityEventを実装したい場合は下のような実装が必要でした。

// 適当なジェネリックなクラス
[Serializable]
public class MyGenericData<T>
{
    public T value;
}
// ジェネリックなクラスを使用する場合、事前に型を定義しておく必要がある
[System.Serializable]
public class UnitySerializeEvent<T> : UnityEvent<T> { }

[System.Serializable]
public class Vector3Data : MyGenericData<Vector3> { }


// 実際にコンポーネントで使用している例
public class MyComponent : MonoBehaviour
{
    [SerializeField] GameObjectEvent gameObjectEv;
    [SerializeField] Vector3Data mydata;
 
    //  コレは使えない
    [SerializeField] MyGenericData<Vector3> vector3Value;
}

f:id:tsubaki_t1:20191101223927j:plain
シリアライズされた値

Unity 2020.1でシリアライズ出来た

 さて、この制限ですがUnity 2020.1で試した所、なんとジェネリックなクラスを継承したクラスを経由せず、直接定義してもシリアライズ出来ました。まぁDictionaryQueueといったクラスは無理でしたが。またシリアライズ出来なかったUnityEventも一度Serializableを設定するクラスを経由すれば使用可能でした。超面倒くさいAssetReferenceTも大本にSerializableを付ければOK。

public class MyComponent : MonoBehaviour
{
    // ジェネリックなクラスを継承したクラスをそのまま定義
    [SerializeField] MyGenericData<Vector3> vector3Value;

    [SerializeField] MyGenericData<string> strValue;

    [SerializeField] MyGenericData<GameObject> objValue;

    [SerializeField] AssetReferenceT<GameObject> assetRefValue;

    [SerializeField] UnitySerializeEvent<GameObject> objEv;
}

// UnityEventにはSerializableが無かったので追加
[System.Serializable]
public class UnitySerializeEvent<T> : UnityEvent<T> { }

f:id:tsubaki_t1:20191101224409j:plain
コンポーネントに直接定義しても行けた

Dictionaryには使えなかった

 これDictionaryにも使えるかなと思いましたが、使えませんでした。

 単純に使うとシリアライズ出来ず、Serializable属性を付けるとInspectorには表示出来ますが値を保持出来ませんでした。

public class MyComponent : MonoBehaviour
{
    [SerializeField] Dictionary<int, string> dic1;

    [SerializeField] MyDicM<int, string> dic2;

    void Reset()
    {
        dic2 = new MyDicM<int, string>();
        dic2.Add(0, "message1");
        dic2.Add(1, "message2");
        dic2.Add(2, "message3");
    }

    void Start() => Debug.Log(dic2.Count); // Reset後なら3が帰るが、シーンを読み直すと0になる
}
[System.Serializable]
public class MyDicM<TKey, TValue> : Dictionary<TKey, TValue> { }

f:id:tsubaki_t1:20191101225643j:plain
Dictionaryの内容が格納されていないか、表示されない

感想

 これでコールバックを用意する度にUnityEventの種類を増やし続ける作業に終止符が…?

 なお検証はアルファ版を使用した為、仕様が変わる可能性があります。