【Unity】SerializeReferenceをつけている型をリネーム・削除する時の注意点

UnityでSerializeReferenceをつけている型をリネーム・削除する時の注意点についてまとめます。

Unity2019.4.29f1
Unity2020.3.22f1
Unity2021.2.3f1

はじめに

SerializeReferenceアトリビュートはUnity2019.3で追加された、参照をシリアライズするための機能です。
基本的な情報は以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

さてこのSerializeReferenceを使う際には、このアトリビュートをつけている型のリネームや削除に注意する必要があります。
本記事ではこれについて説明します。

なおSerializeReferenceは修正が頻繁に入っているため、本記事の情報を使う際には改めてご自身の環境でお試しいただくのが良いと思います。

リネームに起こること

まずSerializeReferenceの仕様として、参照しているクラスの型をアセンブリ名・名前空間・クラス名を使ってシリアライズしています。
具体的には以下のような形式で保存されています。

type: {class: Foo, ns: FooNamespace, asm: FooAssembly}

ここでクラスをリネームしたり、名前空間を変えたり、アセンブリを移動したり、もしくは削除してしまうと、
シリアライズしてある型情報がデシリアライズ時に解決できなくなってしまいます。

簡単なスクリプトを書いてこの挙動を確認してみます。
まず以下のようなインターフェースとその実装クラスを定義します。

public interface ITest
{
}

public class TestImpl : ITest
{
    public int Value;
}

次にこれをシリアライズするScriptableObjectを作成します。

using UnityEditor;
using UnityEngine;

public class Example : ScriptableObject
{
    [SerializeReference] private ITest _test;

    [MenuItem("Example/Create")]
    private static void Create()
    {
        var obj = CreateInstance<Example>();
        obj._test = new TestImpl();
        AssetDatabase.CreateAsset(obj, "Assets/example.asset");
    }
}

Example > CreateからこのScriptableObjecを作成すると、正常に動作していることが確認できます。

f:id:halya_11:20211211163753p:plain
ScriptableObject

この状態でTestImplTestImplRenamedにリネームしてみます。

public interface ITest
{
}

// リネームした
public class TestImplRenamed : ITest
{
    public int Value;
}

するとUnity2019.4.29f1Unity2020.3.22f1では、以下のエラーがコンソールに出力されます。

Unknown managed type referenced: [Assembly-CSharp] .TestImpl 

またUnity2021.2.3f1では以下のような警告がコンソールに出力され、Inspectorにも警告が表示されます。

Missing types referenced from component Example on game object example:
    TestImpl, Assembly-CSharp (1 object)

リネーム時にはMovedFromアトリビュートをつける

この対処法として、UnityEngine.Scripting.APIUpdating.MovedFromAttributeをつけるという手段があります。
これにリネーム前のクラス名を指定すると、クラスがリネーム後のものに正常に置き換えられます。

using UnityEngine.Scripting.APIUpdating;

public interface ITest
{
}

// MovedFromアトリビュートをつけてTestImplからリネームされたことをUnityに教える
[MovedFrom(false, null, null, "TestImpl")]
public class TestImplRenamed : ITest
{
    public int Value;
}

なおアセンブリ名前空間を変える場合には第二引数や第三引数に指定します。

[MovedFrom(false, "TestNamespace", "TestAssembly", "TestImpl")]

クラスを削除する場合

リネームではなくクラスを削除する場合にも注意が必要です。
対象のクラスへの参照がシリアライズされたまま削除しようとするとやはり同様にエラーや警告が出ます。

これに関しては、シリアライズされている参照をすべて削除してからクラスを削除する必要があります。

いずれこの対応は不要になるかも

さて以下のissut trackerを見ると、Unityからのコメントが以下のように掲載されています。

This will require some significant architectural changes which we are looking to address in the 2021 time frame. Currently one way around the issue is to use UnityEngine.Scripting.APIUpdating.MovedFromAttribute to tag the class which was renamed, moved from namespace or assembly.

issuetracker.unity3d.com

これを見るに、MovedFromアトリビュートを使うのは一時的なワークアラウンドであり、
いずれはこれが無くてもリネームや削除できるようになりそうです。

関連

light11.hatenadiary.com

参考

issuetracker.unity3d.com