はじめに
前回紹介したフリーで高機能なエディタ拡張VFWですが、カスタムシリアライゼーションシステムに救われたので紹介してみます。
まあ、非推奨なんですけど、そういって切り捨てるには惜しい機能です。
C#のUnity独自の制約を知らなくて困ったことの一つが、ベースクラスの配列やリストに継承したクラスを入れておいても、ベースクラスに書き換えられてしまうということ。原因に気づいた後色々調べてみても、「そういう仕様」ということでやるなら独自の拡張をしなければならないと思って絶望しかけてたんですが、そういえばVFWにそんな機能があったような? と思い至り試してみました。
問題点(MonoBehaviourの場合)
こんなクラス群を作って、適当なGameObjectに Root.cs を AddComponent してみました。
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);
}
}
}
using System;
[Serializable]
public class SampleBase
{
public string Name = "Hoge";
}
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;
}
}
右端のボタンを押してResetメニューを実行すると、Root.Reset()メソッドが走るので、インスペクターで見ると上の画像のようになってます。Nameは変わってるので正常に初期化できてるはずですが、継承クラスのフィールドが見えないのでなんとも言えませんね。
ところが、シーンを再生してコンソールを見てみるとこうなる。
sample#0: type=SampleBase, False
sample#1: type=SampleBase, False
なんと、ベースクラスに置き換わってしまってるようです。
VFW/BaseBehaviour の場合
Rootクラスが BaseBehaviour を継承するように変えてみました。
VFW はいいですね。継承クラスのフィールドもちゃんと見えてます。(※ResetまたはAddComponentし直してください)
ところが、シーンを再生するとこうなります。
またしてもベースクラスに置き換わってしまいました。
それもそのはず、BaseBehaviourは見栄えこそVFWのシステムを使っていますが、シリアライズはUnity頼みです。Unityで出来ないことはできません。
禁断のBetterBehaviour
非推奨のカスタムシリアライゼーションを使うにはBaseBehaviourではなく、 BetterBehaviour を使います。(BaseScriptableObjectの場合は、 BetterScriptableObject に変える)。
加えて、[SerializeField]の代わりに、[Serialze]を使います。それだけだとインスペクター上に表示されなくなってしまうので、[Show]も付けます。修正したコードは以下の通り。なお、SampleBase, SampleInheritクラスの[Serializable] も削除した方がよいです。
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 という項目が出ているのが、カスタムシリアライゼーションが機能してる証拠です。
この状態でシーンを再生すると・・・
sample#0: type=SampleInherit, True
sample#1: type=SampleInherit, True
いけました! インスペクター上も、再生前の状態が維持されています。
これで問題は解決しました。
まとめ
Unityは継承クラスをうまくシリアライズできないが、VFWのカスタムシリアライゼーションならできる。
カスタムシリアライゼーションを使うには、 MonoBehaviour の代わりに BetterBehaviour を、ScriptableObject の代わりに BetterScriptableObject を使う(Base〜ではなくてBetter〜)。
シリアライズするフィールドには [SerializeField] ではなく [Show,Serialize] を使う。[Serializable]は使わない。
余談
このように、カスタムシリアライゼーションは便利ですが、作者に非推奨を考え直すように要望するのは辞めたほうがいいと思います。おそらく既に何度もそういう議論をしてうんざりしてると思いますので。