Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

前回紹介したフリーで高機能なエディタ拡張VFWですが、カスタムシリアライゼーションシステムに救われたので紹介してみます。
まあ、非推奨なんですけど、そういって切り捨てるには惜しい機能です。

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

問題点(MonoBehaviourの場合)

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

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);
        }
    }
}
SampleBase.cs
using System;

[Serializable]
public class SampleBase
{
    public string Name = "Hoge";
}
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

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

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

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

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

VFW/BaseBehaviour の場合

Rootクラスが BaseBehaviour を継承するように変えてみました。

vfw_root_inspector.jpg

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

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

vfw_root_inspector_after.jpg

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

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

禁断のBetterBehaviour

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

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

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

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

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

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

まとめ

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

余談

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした