見出し画像

Unityゲーム開発で絶対役立つ小ネタスクリプト集3

Unity上で、大量に作った建物パーツの壁や窓のマテリアルをまとめて差し替えるために、マテリアルを差し替えるエディタ拡張を作りました。

MaterialReplaceEditor.cs

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

namespace FUNSET
{
    /// <summary>
    /// 選択したオブジェクトのマテリアルを差し替えます。複数対応可
    /// メニュー-FUNSET-Prefab Material Replacerを開き、 Target materialとReplace materialをセットし、
    /// シーンやヒエラルキーからオブジェクトを選択してください。
    /// 選択したオブジェクトにTarget materialが使用されていると、実行ボタンでReplace materialに置き換えます。
    /// Target materialが空欄の場合、Default-Materialを置き換えます。
    /// アンドゥはできないので注意してください
    /// Open Menu-FUNSET-Prefab Material Replacer, set Target material and Replace material, 
    /// and select an object from the Select an object from the scene or hierarchy.
    /// Replaces the material of the selected object(s).
    /// If Target material is used for the selected object, the Run button replaces it with Replace material.
    /// If Target material is blank, Default-Material will be substituted.
    /// Note that undo is not possible.
    /// </summary>
    public class MaterialReplaceEditor : EditorWindow
    {
        [MenuItem("Tools/FUNSET/Prefab Material Replacer")]

        //Open window
        public static void Open()
        {
            var window = EditorWindow.GetWindow(typeof(MaterialReplaceEditor));
            window.maxSize = new Vector2(400f, 400f);
            window.minSize = new Vector2(200f, 150f);
        }

        Vector2 scrollPos1 = new Vector2(0, 0);

        private List<GameObject> currentSelection = new List<GameObject>();


        [SerializeField]
        private GameObject[] seleobject;


        [SerializeField, Header("Target material")]
        private Material TargetMat;


        [SerializeField, Header("Material to be replaced")]
        private Material ReplaceMat;


        public void OnGUI()
        {


            GUILayout.Label("Material Replacer");

            GUILayout.Label("If the Target material is used for the selected object(s),\n replace it with the Replace material.");

            GUILayout.Label("If Target Material is blank, replace Default Material.");

            GUILayout.Label("Note that undo is not possible.");

            EditorGUILayout.BeginVertical(GUI.skin.box);
            {
                EditorGUILayout.BeginVertical();
             
                    //対象のマテリアル設定
                    
                    TargetMat = (Material)EditorGUILayout.ObjectField("Target Material", TargetMat, typeof(Material), false);
                    ReplaceMat = (Material)EditorGUILayout.ObjectField("Replacement Material", ReplaceMat, typeof(Material), false);
                    
     

                EditorGUILayout.EndVertical();

                //Object selection judgment

                if (Selection.objects.Length > 0)
                {
                    currentSelection = Selection.objects.OfType<GameObject>().ToList();
                }
                else
                {
                    currentSelection.Clear();
                }

                if (Selection.gameObjects.Length == 0)
                {
                    EditorGUILayout.HelpBox("Select one or more similar objects", MessageType.Error, true);
                }
                else
                {
                    GUILayout.Label(Selection.gameObjects.Length+" Object1(s) already set");
                }


                if (Selection.objects.Length > 0 && ReplaceMat != null)
                {
                    bool jikkou = GUILayout.Button("Execute Material Replace", GUILayout.Width(250), GUILayout.Height(30));
                    if (jikkou) { ExeMaterialReplace(); }
                    
                }

            }
            EditorGUILayout.EndVertical();




        }

        /// <summary>
        /// マテリアル差し替えを実行します。子オブジェクトは探しません
        /// Performs material replacement. Does not look for child objects.
        /// </summary>
        void ExeMaterialReplace()
        {
            int countrepmat = 0;

            // Put a list of selected objects into an array
            seleobject = new GameObject[Selection.gameObjects.Length];

            //Process objects in array one by one

            for (int s = 0; s < Selection.gameObjects.Length; s++)
            {

                seleobject[s] = Selection.gameObjects[s];


                Undo.RegisterCompleteObjectUndo(seleobject[s], "MaterialChangeEditor");

                //差し替えられるマテリアルがないか調べて、できるなら差し替える
                //See if there is any material that can be replaced, and if so, replace it.

                var ga = seleobject[s].GetComponent<MeshRenderer>();
                if (ga!=null)
                {
                    Material[] ThisMats = ga.sharedMaterials;

                    for (int i = 0; i < ThisMats.Length; i++)
                    {
                        //Debug.Log("ThisMats["+i+"=" + ThisMats[i]);

                        if (TargetMat == null)
                        {
                            if (ThisMats[i].name == "Default-Material") { ThisMats[i] = ReplaceMat; countrepmat++; }
                        }
                        else
                        {
                            if (ThisMats[i] == TargetMat) { ThisMats[i] = ReplaceMat; countrepmat++; }
                        }
                        
                    }

                    ga.sharedMaterials = ThisMats;
                   
                }


            }

            GUILayout.Label(countrepmat + " Object1(s) replaced");
        }

    }
}

Editorフォルダを作ってこのスクリプトを入れてください。
メニュー-FUNSET-Prefab Material Replacerを開き、 Target materialReplace materialをセットし、シーンやヒエラルキーからオブジェクトを選択してください。実行ボタンが押せるようになり、
選択したオブジェクトにTarget materialが使用されていると、Replace materialに置き換えられます。
Target materialが空欄の場合、Default-Materialを置き換えます。メッシュを含まないオブジェクトを選択されてても問題なく動作します。
アンドゥはできないので注意してください。

ついでに、エディタ拡張でない実行時に機能するマテリアル差し替え機能も作りました。

MaterialReplacer.cs

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

namespace FUNSET
{
    /// <summary>
    /// 実行時に、選択したオブジェクトのマテリアルを差し替えます。複数対応可
    /// 
    /// Determine if the setting is valid and adjust TargetNum and ReplaceNum
    /// 
    /// このスクリプトをシーン上の適当なオブジェクトにアタッチし、
    /// TargetObjectsListに対象のオブジェクトをセット、 Target materialとReplace materialをセットし、
    /// 必要ならOnStartのチェックをtrueにします。
    /// OnStartがtrueだとPlay時に、選択したオブジェクトにTarget materialが使用されていれば、Replace materialに置き換えます。
    /// falseの場合は、メソッドを実行して好きなタイミングで実行できます。
    /// 
    /// Attach this script to the appropriate object in the scene, set TargetObjectsList to
    /// Set the target object in the TargetObjectsList, set the Target material and Replace material, 
    /// and set the OnStart checkbox to true if necessary.
    /// If necessary, set the OnStart checkbox to true.
    /// If OnStart is true, then at play time, if Target material is used for the selected object, it will be replaced with Replace material.
    /// If false, the method can be executed at any time you wish.
    /// 
    /// Target materialとReplace materialは複数セットできて、その中から置き換えを実行する要素を切り替えられます。
    /// Target materialが空欄の場合、Default-Materialを置き換えます。Replace materialは最低一つセットされてないと機能しません。
    /// 
    /// Multiple sets of Target material and Replace material can be used to switch the element to be replaced.
    /// If Target material is blank, replace Default-Material; Replace material must have at least one set to work.
    /// 
    /// 用意されたメソッドは、
    /// ChangeTargetMat(int) TargetMat[]のプリセット番号を変更(デフォルトは0)
    /// ChangeReplaceMat(int) ReplaceMat[]のプリセット番号を変更(デフォルトは0)
    /// ExeMaterialReplace() 置き換え実行
    /// 
    /// The methods provided are
    /// ChangeTargetMat(int) Change the preset number of TargetMat[] (default 0)
    /// ChangeReplaceMat(int) Change the preset number of ReplaceMat[] (default 0)
    /// ExeMaterialReplace() Execute Replace
    /// 
    /// </summary>
    public class MaterialReplacer : MonoBehaviour
    {
        [SerializeField, Header("TargetObjectsList"), Tooltip("")]
        public List<GameObject> TargetObjectsList;

        [SerializeField, Header("Target material")]
        public Material[] TargetMatList;
        public int TargetNum=0;

        [SerializeField, Header("Replace Material")]
        public Material[] ReplaceMatList;
        public int ReplaceNum = 0;

        [SerializeField, Header("Auto exe at Start()"), Tooltip("")]
        bool OnStart;
        public void Awake()
        {
        }
        
        public void Start()
        {
            if (OnStart) { ExeMaterialReplace(); }
        }

        /// <summary>
        /// 置き換え実行 TargetMatList[TargetNum] を ReplaceMatList[ReplaceNum]に置き換えます。
        /// replace execution.Replace TargetMatList[TargetNum] with ReplaceMatList[ReplaceNum]
        /// </summary>
        public void ExeMaterialReplace()
        {
            //設定が有効か判定して、TargetNum、ReplaceNumを調整する 
            //Determine if the setting is valid and adjust TargetNum and ReplaceNum

            if (TargetObjectsList.Count == 0) return;
            if (ReplaceMatList.Length == 0) return;

            TargetNum= Mathf.Clamp(TargetNum, 0, TargetMatList.Length - 1);
            ReplaceNum = Mathf.Clamp(ReplaceNum, 0, ReplaceMatList.Length-1);

            int countrepmat = 0;

            //差し替えられるマテリアルがないか調べて、できるなら差し替える
            //See if there is any material that can be replaced, and if so, replace it.

            for (int s = 0; s < TargetObjectsList.Count; s++)
            {

                var ga = TargetObjectsList[s].GetComponent<MeshRenderer>();
                if (ga != null)
                {
                    Material[] ThisMats = ga.sharedMaterials;


                    for (int i = 0; i < ThisMats.Length; i++)
                    {
                        //Debug.Log("ThisMats["+i+"=" + ThisMats[i]);

                        if (TargetMatList.Length == 0)
                        {
                            if (ThisMats[i].name == "Default-Material") { ThisMats[i] = ReplaceMatList[ReplaceNum]; countrepmat++; }
                        }
                        else
                        {
                            if (ThisMats[i] == TargetMatList[TargetNum]) { ThisMats[i] = ReplaceMatList[ReplaceNum]; countrepmat++; }
                        }

                    }

                    ga.sharedMaterials = ThisMats;

                }

#if UNITY_EDITOR
                Debug.Log(countrepmat + " Object1(s) replaced");
#endif
            }

        }

        /// <summary>
        /// ChangeTargetMat(int) TargetMat[]のプリセット番号を変更(デフォルトは0)
        /// </summary>
        /// <param name="a"></param>
        public void ChangeTargetMat(int a) { TargetNum = a; }

        /// <summary>
        /// ChangeReplaceMat(int) ReplaceMat[]のプリセット番号を変更(デフォルトは0)
        /// </summary>
        /// <param name="a"></param>
        public void ChangeReplaceMat(int a) { ReplaceNum = a; }

    }
}


このスクリプトをシーン上の適当なオブジェクトにアタッチし、
TargetObjectsListに対象のオブジェクトをセット、 Target materialReplace materialをセットし、必要ならOnStartのチェックをtrueにします。
OnStartがtrueだとPlay時に、選択したオブジェクトにTarget materialが使用されていれば、Replace materialに置き換えます。
falseの場合は、メソッドを実行して好きなタイミングで実行できます。
Target materialとReplace materialは配列で複数セットできて、その中から置き換えを実行する要素を一つ選んで切り替えられます。(複数まとめて差し替えるわけではありません)
Target materialが空欄の場合、Default-Materialを置き換えます。Replace materialは最低一つセットされてないと機能しません。

メソッドは、
ChangeTargetMat(int) TargetMat[]のプリセット番号を変更(デフォルトは0)
ChangeReplaceMat(int) ReplaceMat[]のプリセット番号を変更(デフォルトは0)
ExeMaterialReplace() マテリアル置き換えを実行

このスクリプトは自由にコピペして改変して使えます。
子・孫オブジェクトまで取得するとか、シーン上からオブジェクトを自動取得とかマテリアルを複数一括差し替えとか改造するといいと思います。

この記事が気に入ったらサポートをしてみませんか?