【Unity】エディタ拡張でマテリアルを一度に置換する

投稿者: | 2023-04-22

エディタ拡張で、シーン上のゲームオブジェクトのマテリアルを一括で置換してみました。

エディタウィンドウで2つのマテリアルを指定し、片方のマテリアルが設定されたゲームオブジェクトがシーンにあれば、もう片方のマテリアルに置き換えます。

エディタウィンドウを作る

エディタウィンドウはMenuItem属性を使って、メインメニューから表示できるようにしています。

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class SwapMaterials : EditorWindow
{
    // エディタウィンドウを開く
    [MenuItem("Window/Test/SwapMaterials")]
    public static void CreateWindow()
    {
        EditorWindow.GetWindow<SwapMaterials>();
    }

2つのマテリアルと、マテリアルを置き換えたゲームオブジェクトのリストの変数を宣言します。

    // 置き換えるマテリアル
    Material material_to;
    Material material_from;

    // マテリアルを置き換えたゲームオブジェクトのリスト
    List<GameObject> swappedObjs = new List<GameObject>();

エディタウィンドウでは、まずEditorGUILayout.ObjectFieldメソッドを使って2つのマテリアルを指定するための領域を表示しています。

    private void OnGUI()
    {
        // 置き換え元のマテリアルを取得
        material_from = (Material)EditorGUILayout.ObjectField("From" , material_from, typeof(Material), false);
        
        // 置き換え先のマテリアルを取得
        material_to = (Material)EditorGUILayout.ObjectField("To" , material_to, typeof(Material), false);

EditorGUILayout.ObjectFieldメソッドでは表示するテキストやオブジェクト、割り当て可能な型、シーンのオブジェクトの割当を可能にするかどうかのbool値などを指定しています。

2つのマテリアルが指定されたら、「置換」ボタンと「クリア」ボタンを表示します。置換ボタンを押すとまずリストをクリアします。

        if (material_from != null && material_to != null)
        {
            // 置換ボタンを押すと
            if (GUILayout.Button("置換"))
            {
                // リストをクリア
                swappedObjs.Clear();

そして、ヒエラルキービューにある全てのゲームオブジェクトを検索して、一つずつ処理していきます。その中ではまずレンダラーコンポーネントを取得して、なければ次のゲームオブジェクトの処理に移ります。

                foreach (GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[])
                {
                    // ヒエラルキーにあるゲームオブジェクト
                    if (!EditorUtility.IsPersistent(go.transform.root.gameObject) && !(go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave))
                    {

                        // レンダラーコンポーネントを取得
                        var r = go.GetComponent<Renderer>();
                        
                        // レンダラーコンポーネントがなければ次のゲームオブジェクトを処理
                        if (r == null) continue;

                        // ...
                    }
                }

レンダラーコンポーネントがあれば、マテリアルを置き換えたかどうかのbool型の変数と、レンダラーに設定されているマテリアルの配列の複製を用意します。

そして、複製の配列を一つずつみていって、指定のマテリアルがあれば、それを置換先のマテリアルに置き換えて、bool値をtrueにします。

                        // マテリアルを置き換えたかどうかのフラグ
                        bool flag = false;

                        // レンダラーに設定されているマテリアル配列をコピー
                        var materials = r.sharedMaterials;
                        
                        // マテリアルを一つずつ見る
                        for(int i = 0; i < materials.Length; i++)
                        {
                            // 置き換え元のマテリアルと同じものがあれば
                            if (r.sharedMaterials[i] == material_from)
                            {
                                // 置き換え先のマテリアルと置き換え
                                materials[i] = material_to;
                                
                                // フラグをtrueにする
                                flag = true;
                            }
                        }

bool変数がfalseなら置換が行われていないので、次のゲームオブジェクトに移ります。trueの場合は、レンダラーのマテリアル配列を複製と置き換えます。そして、そのゲームオブジェクトをリストに追加しています。

                        // 置き換えなければ次を処理
                        if (!flag) continue;

                        // 置き換えたマテリアル配列をレンダラーに設定。
                        r.sharedMaterials = materials;

                        // 置き換えたゲームオブジェクトのリストに追加
                        swappedObjs.Insert(0, go);

                        // 保存対象にする
                        EditorUtility.SetDirty(go);
                    }
                }
            }

その後、EditorUtility.SetDirtyメソッドを使って、このゲームオブジェクトを保存対象にしています。これでマテリアルを置換するとタイトルバーに「*」マークが表示され保存できるようになります。

保存対象にしない
保存対象にする

クリアボタンを押すとリストをクリアします。

            else if(GUILayout.Button("クリア"))
            {
                // リストをクリア
                swappedObjs.Clear();
            }

リストにゲームオブジェクトが追加されている場合、ボタンの下にそのゲームオブジェクトを並べています。リストを一つずつみて、ゲームオブジェクトの名前とボタンを表示します。ボタンが押されるとそのゲームオブジェクトが選択されます。

            if(swappedObjs != null || swappedObjs.Count > 0)
            {
                // 一つずつ処理
                foreach (var o in swappedObjs)
                {
                    // ゲームオブジェクトが削除されていれば
                    if (o == null)
                    {
                        // リストから削除
                        swappedObjs.Remove(o);

                        // 処理を終了
                        break;
                    }

                    // ボタンを表示する。ボタン上にゲームオブジェクトの名前を表示。
                    if (GUILayout.Button(o.name, EditorStyles.textField))
                    {
                        // ボタンをクリックすると、そのゲームオブジェクトを選択する。
                        Selection.activeGameObject = o;
                    }
                }
            }
        }
    }
}

リストにあるゲームオブジェクトがシーンで削除されるとエラーになるので、ボタンを表示する前に削除されているか確認して、削除されていたらリストから除外し、OnGUIメソッドの処理を終了しています。

マテリアルを置き換える

シーンに5つのCubeを追加しました。

それぞれには青色か緑色のマテリアルが設定されています。

エディタウィンドウを表示したら、プロジェクトウィンドウから2つの領域にマテリアルをドラッグアンドドロップします。青色のマテリアルから赤色のマテリアルに置き換えます。

マテリアルを割り当てるとボタンが表示されるので、置換ボタンをクリックします。すると青色のマテリアルだけが赤に変わります。

リストのボタンを選択すると、ヒエラルキービューやシーンビューでゲームオブジェクトが選択されています。

これで、シーンのマテリアルを一括で置き換えることができました。

スクリプト

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class SwapMaterials : EditorWindow
{
    // エディタウィンドウを開く
    [MenuItem("Window/Test/SwapMaterials")]
    public static void CreateWindow()
    {
        EditorWindow.GetWindow<SwapMaterials>();
    }

    // 置き換えるマテリアル
    Material material_to;
    Material material_from;

    // マテリアルを置き換えたゲームオブジェクトのリスト
    List<GameObject> swappedObjs = new List<GameObject>();

    private void OnGUI()
    {
        // 置き換え元のマテリアルを取得
        material_from = (Material)EditorGUILayout.ObjectField("From" , material_from, typeof(Material), false);
        
        // 置き換え先のマテリアルを取得
        material_to = (Material)EditorGUILayout.ObjectField("To" , material_to, typeof(Material), false);

        // マテリアルが取得されたら
        if (material_from != null && material_to != null)
        {
            // 置換ボタンを押すと
            if (GUILayout.Button("置換"))
            {
                // リストをクリア
                swappedObjs.Clear();

                // すべてのゲームオブジェクト
                foreach (GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[])
                {
                    // ヒエラルキーにあるゲームオブジェクト
                    if (!EditorUtility.IsPersistent(go.transform.root.gameObject) && !(go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave))
                    {

                        // レンダラーコンポーネントを取得
                        var r = go.GetComponent<Renderer>();
                        
                        // レンダラーコンポーネントがなければ次のゲームオブジェクトを処理
                        if (r == null) continue;

                        // マテリアルを置き換えたかどうかのフラグ
                        bool flag = false;

                        // レンダラーに設定されているマテリアル配列をコピー
                        var materials = r.sharedMaterials;
                        
                        // マテリアルを一つずつ見る
                        for(int i = 0; i < materials.Length; i++)
                        {
                            // 置き換え元のマテリアルと同じものがあれば
                            if (r.sharedMaterials[i] == material_from)
                            {
                                // 置き換え先のマテリアルと置き換え
                                materials[i] = material_to;
                                
                                // フラグをtrueにする
                                flag = true;
                            }
                        }

                        // 置き換えなければ次を処理
                        if (!flag) continue;

                        // 置き換えたマテリアル配列をレンダラーに設定。
                        r.sharedMaterials = materials;

                        // 置き換えたゲームオブジェクトのリストに追加
                        swappedObjs.Insert(0, go);

                        // 保存対象にする
                        EditorUtility.SetDirty(go);
                    }
                }
            }
            // クリアボタンを押す
            else if(GUILayout.Button("クリア"))
            {
                // リストをクリア
                swappedObjs.Clear();
            }

            // 置換されたゲームオブジェクトがあるとき
            if(swappedObjs != null || swappedObjs.Count > 0)
            {
                // 一つずつ処理
                foreach (var o in swappedObjs)
                {
                    // ゲームオブジェクトが削除されていれば
                    if (o == null)
                    {
                        // リストから削除
                        swappedObjs.Remove(o);

                        // 処理を終了
                        break;
                    }

                    // ボタンを表示する。ボタン上にゲームオブジェクトの名前を表示。
                    if (GUILayout.Button(o.name, EditorStyles.textField))
                    {
                        // ボタンをクリックすると、そのゲームオブジェクトを選択する。
                        Selection.activeGameObject = o;
                    }
                }
            }
        }
    }
}

コメントを残す

メールアドレスが公開されることはありません。