0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RuntimeでPrefabを保存したい

0
Posted at

はじめに

UnityでのEditorを作る際にRuntime中でのPrefabを保存する必要のあるEditorを作ることがあったので、その方法を紹介したいと思います。

Prefabのパスを取得する

実は、Runtime中のGameObjectからPrefabのパスを取得する方法は存在しません。なので本来はPrefabのパスを直接指定してあげる必要がありますが、「Prefabの名前がAssetの中で一意である」限りは取得することができます。

AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets(filter).First())

これで取得することができます。

Prefabとして保存する

Prefabを保存する方法にもいくつか注意が必要です。

var instance = PrefabUtility.LoadPrefabContents(PrefabPath);

// 値の変更
instance.transform.position = Vector3.zero;

PrefabUtility.SaveAsPrefabAsset(instance, PrefabPath);
PrefabUtility.UnloadPrefabContents(instance);

上記が基本的なPrefabの保存方法になります。この手順はPrefabをダブルクリックしたときの画面(Prefabモード)に入っているイメージです。最後に忘れずUnloadPrefabContents()も必要です。

また、Prefabの入れ子にしたい場合、通常のInstantiate()ではGameObjectとして生成されてしまうため、InstantiatePrefab()として生成する必要があります。

var instance = PrefabUtility.LoadPrefabContents(PrefabPath);

// Prefabの入れ子
PrefabUtility.InstantiatePrefab(_cubePrefab, instance.transform);

PrefabUtility.SaveAsPrefabAsset(instance, PrefabPath);
PrefabUtility.UnloadPrefabContents(instance);

これでRuntimeでのPrefabの保存機能が実装できました。次に使いやすいクラスを作成していきます。

RuntimePrefabUtility

コード全体

畳んであります
TestEditor.cs
using System.Threading;
using UnityEngine;

public class TestEditor : MonoBehaviour
{
    [SerializeField]
    private GameObject _baseObj;
    [SerializeField]
    private GameObject _cubePrefab;
    [SerializeField]
    private GameObject _spherePrefab;


    private void Start()
    {
        var token = destroyCancellationToken;

        var rpu = new RuntimePrefabUtility(_baseObj);
        rpu.Save(token, RuntimePrefabUtility.SaveTarget.GameObject | RuntimePrefabUtility.SaveTarget.Prefab, Save);
    }

    private bool Save(CancellationToken ct, RuntimePrefabUtility.SaveTarget saveTarget, GameObject instance)
    {
        var cube = UnityEditor.PrefabUtility.InstantiatePrefab(_cubePrefab, instance.transform) as GameObject;
        cube.transform.position = new Vector3(0, -5, 0);
        return true;
    }
}

RuntimePrefabUtility.cs
#if UNITY_EDITOR

using Cysharp.Threading.Tasks;
using System;
using System.Linq;
using System.Threading;
using UnityEditor;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class RuntimePrefabUtility
{
    public enum SaveTarget
    {
        None       = 0 << 0,
        GameObject = 1 << 0,
        Prefab     = 1 << 1,
    }

    public GameObject SceneObj { get; private set; }
    public string PrefabPath { get; private set; }


    public RuntimePrefabUtility(GameObject obj, string path)
    {
        SceneObj = obj;
        PrefabPath = path;
    }
    public RuntimePrefabUtility(GameObject obj)
    {
        SceneObj = obj;
        PrefabPath = FindAssetFromFilter(obj.name);
    }

    public static string FindAssetFromFilter(string filter)
        => AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets(filter).First());

    public void Save(CancellationToken ct, SaveTarget saveTarget, Func<CancellationToken, SaveTarget, GameObject, bool> save)
    {
        if (saveTarget.HasFlag(SaveTarget.GameObject))
        {
            save(ct, saveTarget, SceneObj);
        }
        if (saveTarget.HasFlag(SaveTarget.Prefab))
        {
            var instance = PrefabUtility.LoadPrefabContents(PrefabPath);
            if (save(ct, saveTarget, instance))
            {
                PrefabUtility.SaveAsPrefabAsset(instance, PrefabPath);
            }
            PrefabUtility.UnloadPrefabContents(instance);
        }
    }
}

#endif //UNITY_EDITOR

ここではいくつかのポイントを説明します。

まずコンストラクタについて、このプロジェクト内で_baseObjのオブジェクト名はAsset内で一意であることが確定しているので、オブジェクト名から前述した方法でパスを取得しています。

次にSaveTargetがPrefabとGameObjectで定義されている理由ですが、Unityの仕様上Scene上に置かれたPrefabはRuntimeになった瞬間Prefabとの接続が切れるので、Runtime中に元のPrefabを変更してもScene上のオブジェクトには影響しません。なのでSceneのObject用とPrefab用として2回呼び出す必要があります。

このテクニックを使い、逆に保存するタイミングを任意で変更できるようになります。
例えば、GameViewで色々変更(SaveTarget.GameObject)したけど、変更を破棄したいとなれば、そのままRuntimeをExitすれば変更は破棄されます。

あとはSave()の中に処理を追加していく感じになります。

おわりに

通常ならPrefabを編集したい場合はEditorで編集できるのが一番ですが、プロジェクトの技術的制約からRuntimeでのPrefabEditorが必要になる場合があります。Editorで編集することが難しい場合はRuntimeでもこういった手法で保存可能なので、この記事をご覧の方は参考にしてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?