[Unity][VFW]継承クラスを入れたベース型の配列・リストを保存する

# はじめに
前回紹介した[フリーで高機能なエディタ拡張VFW](https://qiita.com/Shinoda_Naoki/items/7a3205ae3e40d3fc58ab)ですが、**カスタムシリアライゼーションシステム**に救われたので紹介してみます。
まあ、**非推奨**なんですけど、そういって切り捨てるには惜しい機能です。

C#のUnity独自の制約を知らなくて困ったことの一つが、**ベースクラスの配列やリストに継承したクラスを入れておいても、ベースクラスに書き換えられてしまう**ということ。原因に気づいた後色々調べてみても、「そういう仕様」ということでやるなら独自の拡張をしなければならないと思って絶望しかけてたんですが、そういえばVFWにそんな機能があったような？　と思い至り試してみました。

# 問題点（MonoBehaviourの場合）

こんなクラス群を作って、適当なGameObjectに Root.cs を AddComponent してみました。

```csharp:Root.cs
using UnityEngine;
using Vexe.Runtime.Types;

public class Root : MonoBehaviour
{
    [SerializeField]
    private SampleBase[] Samples;

    private void Reset()
    {
        if (Samples == null || Samples.Length == 0)
        {
            Samples = new SampleBase[]
            {
                new SampleInherit("Fuga", 456),
                new SampleInherit("Piyo", 789)
            };
        }
    }

    private void Start()
    {
        int i = 0;
        foreach (var sample in Samples)
        {
            var inherited = sample as SampleInherit;
            Debug.LogFormat("sample#{0}: type={1}, {2}", i++, sample.GetType().Name, inherited != null);
        }
    }
}
```

```csharp:SampleBase.cs
using System;

[Serializable]
public class SampleBase
{
    public string Name = "Hoge";
}
```

```csharp:SampleInherit.cs
using System;

[Serializable]
public class SampleInherit : SampleBase
{
    public int Number = 123;

    /// <summary>
    /// デシリアライズ用
    /// </summary>
    private SampleInherit(){}

    public SampleInherit(string name, int number)
    {
        Name = name;
        Number = number;
    }
}
```

![root_inspector.jpg](https://qiita-image-store.s3.amazonaws.com/0/75035/37d60fb1-683e-4c45-cd8e-f43be735c8b0.jpeg)

右端の:gear:ボタンを押してResetメニューを実行すると、Root.Reset()メソッドが走るので、インスペクターで見ると上の画像のようになってます。Nameは変わってるので正常に初期化できてるはずですが、継承クラスのフィールドが見えないのでなんとも言えませんね。

ところが、シーンを再生してコンソールを見てみるとこうなる。

```:console
sample#0: type=SampleBase, False
sample#1: type=SampleBase, False
```

なんと、ベースクラスに置き換わってしまってるようです。

# VFW/BaseBehaviour の場合

Rootクラスが **BaseBehaviour** を継承するように変えてみました。
 
![vfw_root_inspector.jpg](https://qiita-image-store.s3.amazonaws.com/0/75035/f68e7adf-bd7c-d36b-b21c-ae51f3627bfe.jpeg)

VFW はいいですね。継承クラスのフィールドもちゃんと見えてます。（※ResetまたはAddComponentし直してください）

ところが、シーンを再生するとこうなります。

![vfw_root_inspector_after.jpg](https://qiita-image-store.s3.amazonaws.com/0/75035/1d18667e-b968-dd06-5090-71648a973151.jpeg)

またしてもベースクラスに置き換わってしまいました。

それもそのはず、BaseBehaviourは見栄えこそVFWのシステムを使っていますが、シリアライズはUnity頼みです。Unityで出来ないことはできません。

# 禁断のBetterBehaviour

　**非推奨のカスタムシリアライゼーションを使う**にはBaseBehaviourではなく、 **BetterBehaviour** を使います。（BaseScriptableObjectの場合は、 **BetterScriptableObject** に変える）。

加えて、[SerializeField]の代わりに、[**Serialze**]を使います。それだけだとインスペクター上に表示されなくなってしまうので、[Show]も付けます。修正したコードは以下の通り。なお、SampleBase, SampleInheritクラスの[Serializable] も削除した方がよいです。

```csharp:Root.cs(修正後)
using UnityEngine;
using Vexe.Runtime.Types;

public class Root : BetterBehaviour
{
    [Show, Serialize]
    private SampleBase[] Samples;

    private void Reset()
    {
        if (Samples == null || Samples.Length == 0)
        {
            Samples = new SampleBase[]
            {
                new SampleInherit("Fuga", 456),
                new SampleInherit("Piyo", 789)
            };
        }
    }

    private void Start()
    {
        int i = 0;
        foreach (var sample in Samples)
        {
            var inherited = sample as SampleInherit;
            Debug.LogFormat("sample#{0}: type={1}, {2}", i++, sample.GetType().Name, inherited != null);
        }
    }
}
```

AddComponentしなおして、インスペクターで見るとこうなりました。
Serialization Data という項目が出ているのが、カスタムシリアライゼーションが機能してる証拠です。

![vfw_better_inspector.jpg](https://qiita-image-store.s3.amazonaws.com/0/75035/c94d3f3f-3690-2ec6-ab84-71914bbe24a7.jpeg)

この状態でシーンを再生すると・・・

```:console
sample#0: type=SampleInherit, True
sample#1: type=SampleInherit, True
```

いけました！　インスペクター上も、再生前の状態が維持されています。
これで問題は解決しました。

# まとめ
Unityは継承クラスをうまくシリアライズできないが、VFWのカスタムシリアライゼーションならできる。
カスタムシリアライゼーションを使うには、 MonoBehaviour の代わりに BetterBehaviour を、ScriptableObject の代わりに　BetterScriptableObject を使う（Base〜ではなくて**Better**〜）。
シリアライズするフィールドには [SerializeField] ではなく [Show,Serialize] を使う。[Serializable]は使わない。

### 余談
このように、カスタムシリアライゼーションは便利ですが、作者に非推奨を考え直すように要望するのは辞めたほうがいいと思います。おそらく既に何度もそういう議論をしてうんざりしてると思いますので。