[Unity] 別名でシリアライズされた値のシリアライズ名を強制的に書き換える

# 概要

SerializeFieldのフィールド名を変えると、Unity内部ではフィールド名をキーとして値をシリアライズしているため、参照が解決できなくなり値が失われてしまう。
そこで[FormerlySerializedAsAttribute](https://docs.unity3d.com/ja/current/ScriptReference/Serialization.FormerlySerializedAsAttribute.html)を用いることで、リネーム前のシリアライズ値を利用することが可能になる。

```cs
public class MyClass : MonoBehaviour
{
    [FormerlySerializedAs("myValue")]
    string m_MyValue;
    public string myValue
    {
        get { return m_MyValue; }
        set { m_MyValue = value; }
    }
}
```

しかし、FormerlySerializedAsAttributeの機能はあくまで**別名でシリアライズされた値を読む**ことだけであり、自動的にシリアライズ名のリネームを行ってくれるという気の利いた機能までは持っていない。
シリアライズ名の書き替えが行われるには、何らかの理由で対象のオブジェクトが再保存される必要がある。オブジェクトが再保存される際には新しいシリアライズ名を用いてシリアライズが行われる。

従って、**全てのオブジェクトのシリアライズ名を書き換えなければFormerlySerializedAsAttributeをコード上から削除することはできない**。
これはあまり好ましくないため、全てのオブジェクトのシリアライズ名を強制的に書き換える方法を考える。

# 解決法

以下の手順によって解決ができる。

1. 変数名を変え、FormerlySerializedAsAttributeを設定する
2. 対象のオブジェクトを含む全てのアセットを取得する
3. [EditorUtility.SetDirty](https://docs.unity3d.com/ja/current/ScriptReference/EditorUtility.SetDirty.html)により再保存フラグを立てる
4. [AssetDatabase.SaveAssets](https://docs.unity3d.com/ja/current/ScriptReference/AssetDatabase.SaveAssets.html)で全てのアセットを再保存する
5. FormerlySerializedAsAttributeを削除する

SerializeFieldを持てるのはMonobehaviourとScriptableObjectの2種類あるが、それぞれで2の対処法が異なる。

## ScriptableObjectの場合

ScriptableObjectの場合、必ず単体のアセットとして存在しているため、[AssetDatabase.FindAssets](https://docs.unity3d.com/jp/current/ScriptReference/AssetDatabase.FindAssets.html)を使うことで簡単に全てのアセットを取得することができる。

```cs
[MenuItem("Tools/UpdateMyScriptableObject")]
public static void UpdateMyScriptableObject()
{
    // 全ての対象アセットを取得する
    var assetGUIDs = AssetDatabase.FindAssets("t:MyScriptableObject");
    foreach (var assetGUID in assetGUIDs)
    {
        var assetPath = AssetDatabase.GUIDToAssetPath(assetGUID);
        var asset = AssetDatabase.LoadAssetAtPath<MyScriptableObject>(assetPath);
        // 再保存フラグを立てる
        EditorUtility.SetDirty(asset);
    }
    // 再保存する
    AssetDatabase.SaveAssets();
}
```



## MonoBehaviourの場合

MonoBehaviourの場合は少し厄介になる。
対象のコンポーネントを含む全てのSceneまたはPrefabを取得する必要があるが、そのためには**全てのSceneとPrefabを走査する**必要がある。

```cs
[MenuItem("Tools/UpdateMyMonoBehaviour")]
public static void UpdateMyMonoBehaviour()
{
    // Prefabを走査
    var prefabGUIDs = AssetDatabase.FindAssets("t:Prefab");
    foreach (var prefabGUID in prefabGUIDs)
    {
        var assetPath = AssetDatabase.GUIDToAssetPath(prefabGUID);
        var asset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
        var component = asset.GetComponentInChildren<MyMonoBehaviour>();
        if (component != null)
        {
            // MyMonoBehaviourを持つ場合、再保存フラグを立てる
            EditorUtility.SetDirty(asset);
        }
    }
    AssetDatabase.SaveAssets();

    // Sceneを走査
    var sceneGUIDs = AssetDatabase.FindAssets("t:Scene");
    foreach (var sceneGUID in sceneGUIDs)
    {
        var scenePath = AssetDatabase.GUIDToAssetPath(sceneGUID);
        var scene = EditorSceneManager.OpenScene(scenePath);
        var rootGameObjects = scene.GetRootGameObjects();

        foreach (var go in rootGameObjects)
        {
            var component = go.GetComponentInChildren<MyMonoBehaviour>();
            if (component != null)
            {
                // MyMonoBehaviourを持つ場合、再保存する
                EditorSceneManager.MarkSceneDirty(scene);
                EditorSceneManager.SaveScene(scene);
                break;
            }
        }
    }
}
```

予め影響範囲がある程度分かっている場合は、FindAssetsの第2引数にアセットのディレクトリを指定するなどで走査範囲を狭めることで処理時間を短縮することができる。


# 参考

- [Serialization\.FormerlySerializedAsAttribute \- Unity スクリプトリファレンス](https://docs.unity3d.com/ja/current/ScriptReference/Serialization.FormerlySerializedAsAttribute.html)
- [AssetDatabase\-FindAssets \- Unity スクリプトリファレンス](https://docs.unity3d.com/jp/current/ScriptReference/AssetDatabase.FindAssets.html)