【Unity Nested Prefab】ランタイム時インスタンスの変更を保存する

AvatarPosted by

こんにちは、Unityエンジニアの楊です!

今回はUnityのNested Prefabsのインスタンスをランタイム時に保存する方法について紹介したいと思います。

今までのランタイム時保存機能

Unity2018.3まではランタイム中に生成したPrefabのインスタンスに対してHierarchy上のApplyボタンを押すことで、「一括保存」が可能でしたが、新しいワークフローでは一括保存できなくなりました。そうすると、Unityを実行して確認しながら編集することが不可能になります。

ランタイム時におけるPrefabのふるまい

実際に「ランタイム時に保存できないのはなぜか」を調べていきたいと思います。

上図を見ると、実行ボタンを押すと、Hierarchyに置いているNested Prefabインスタンス 「sample_prefab」の色が変わっています。これはNested Prefabのインスタンスは一般のインスタンスになることを意味しています。

この時の「インスタンス」とProject内の「Prefab」の関連性を調べてみることにします。下記のコードを入れてみます。

using UnityEditor;
using UnityEngine;

public static class NestedPrefabConnectionTest
{
    [MenuItem("CONTEXT/Transform/GetPrefabPath")]
    public static void GetPrefabPath()
    {
        var prefab = PrefabUtility.GetCorrespondingObjectFromSource(Selection.activeGameObject);
        if (prefab != null)
        {
            string prefabPath = AssetDatabase.GetAssetPath(prefab);
            Debug.Log(prefabPath);
        }
        else
        {
            Debug.Log(prefab);
        }
    }
}

HierarchyにあるPrefabのインスタンスに対して実験しましょう。

ランタイムではない時

Assets/sample_prefab.prefab

ランタイム時

Null

以上のことから「GetPrefabPath」によってランタイム時にインスタンスとPrefabの関連性を切ることにより、Prefabのパスが取得できなくなったため、ランタイム時には保存できなくなってしまったことが確認できました。

解決策

インスタンスとPrefabの関連性を解決する方法として、内部的にランタイム実行前にPrefabのパスを保存しておき、ランタイム時に事前に保存されたパスを利用してロードするようにします。

今回はランタイム時に気軽に保存できるようにするためにボタンを作ってみました 。

using UnityEngine;

public class RuntimeSave : MonoBehaviour
{
    public string PrefabPath;
}

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(RuntimeSave))]
public class RuntimeSaveInspector : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        var runtimeSave = target as RuntimeSave;
        if (runtimeSave != null)
        {
            if (!Application.isPlaying)
            {
                var prefab = PrefabUtility.GetCorrespondingObjectFromSource(runtimeSave);
                var path = AssetDatabase.GetAssetPath(prefab);
                runtimeSave.PrefabPath = path;
                if (prefab != null)
                {
                    prefab.PrefabPath = path;
                }
            }

            if (GUILayout.Button("Save Prefab"))
            {
                var go = runtimeSave.gameObject;
                PrefabUtility.SaveAsPrefabAsset(go, runtimeSave.PrefabPath);
            }
        }
    }
}

Prefab Pathが自動的に取得され、ランタイム時にSave Prefabを押すとPrefabを一括保存できるようになります。

まとめ

今回は、Nested Prefabs がランタイム時に Prefabインスタンスの変更を保存出来るようにする方法をご紹介させていただきました。

ただ、この方法は安全に運用するためのNested Prefabワークフローを無視した動作を強いることになるため、特にNested Prefabである子、孫を追加して親Prefabを保存する場合は細心の注意が必要です。

正しくNested Prefabについて理解し、効率よく開発を進めましょう。