エディタ拡張で、シーン上のゲームオブジェクトのマテリアルを一括で置換してみました。
エディタウィンドウで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;
}
}
}
}
}
}