Programming Serendipity

プログラミングを中心に種々雑多に書き留めます

ヒエラルキーにある全てのプレハブをApplyするエディター拡張

実行してみたらEditorと動き違う…と思ったらプレハブがApplyされてなかった!(泣) ということ、ありませんか?(私はあります) ということで、安全のために、全てのプレハブの更新を適用するスクリプトを作ってみました。続きからどうぞ。

まずは、プレハブの判定用に以下のようなスクリプトを用意します。(EditorExtensionMethods.cs) Editorフォルダ以下に保存してください。

using UnityEngine;
using UnityEditor;

/// <summary>
/// UnityEditorを使用する拡張メソッド
/// </summary>
public static class EditorExtensionMethods 
{
    /// <summary>
    /// プレハブかどうかを判定
    /// </summary>
    /// <param name="self">対象</param>
    /// <returns>プレハブならtrue, otherwise false</returns>
    /// <remarks>
    /// taken from: http://answers.unity3d.com/questions/218429/how-to-know-if-a-gameobject-is-a-prefab.html 
    /// GetPrefabParent() = A, GetPrefabObject() = Bとすると、
    ///                                          |   A      |     B     |
    /// 1. projectビューにあるプレハブ            |   null    | non-null |
    /// 2. hierarchyにインスタンス化されたプレハブ | non-null | non-null  |
    /// 3. hierarchyにある通常のGameObject        |   null   |   null   |
    /// になっている模様
    /// </remarks>
    public static bool IsPrefab(this GameObject self)
    {
        return PrefabUtility.GetPrefabParent(self) == null && PrefabUtility.GetPrefabObject(self) != null;
    }

    public static bool IsPrefabInstance(this GameObject self)
    {
        return PrefabUtility.GetPrefabParent(self) != null && PrefabUtility.GetPrefabObject(self) != null;
    }
}

ロジックは↑のコメントに大体書きましたが、ヒエラルキーにあるプレハブのインスタンスの場合、GetPrefabParent()null以外、かつGetPrefabObject()null以外になるので、それを使って判定しています。 単にプロジェクトにあるプレハブの場合はGetPrefabParent()がnullになります。

そして、メインの部分です。適当なスクリプトに入れてください。(こちらもEditor以下へ)

/// <summary>
/// 全てのプレハブをApplyする
/// </summary>
[MenuItem("Tools/Apply All Prefab Instances")]
public static void ApplyAllPrefabInstances()
{
    Debug.Log("Start ApplyAllPrefabInstances");
    var rootObjs = SceneManager.GetActiveScene().GetRootGameObjects();
    var appliedCount = 0;
    foreach (var item in rootObjs)
    {
        // プレハブのインスタンスかどうかをチェック
        if (item.IsPrefabInstance())
        {
            // 変更がある場合のみ置き換える
            if (PrefabUtility.GetPropertyModifications(item).Length > 0)
            {
                PrefabUtility.ReplacePrefab(item, PrefabUtility.GetPrefabParent(item));
                appliedCount++;
            }
        }
    }
    Debug.LogFormat("End ApplyAllPrefabInstances Replaced count : {0}", appliedCount);
}

シーンのルートオブジェクトを走査してプレハブのインスタンスかつ変更がある場合に適用します。 と書きましたが、実はこれ変更がなくても一番奥のif文が通るケースがあります。よくわかりません。 まあ、変更がないならないで更新してもそのままですし、問題はないのですが。

って、今書いてて気づきましたが、これ階層の下にあるプレハブに適用できないですね…
たぶんTransformでループを回して調べ上げたらいけると思いますが、私は疲れたので今日はこの辺で。