using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.IO; public class MaterialAndTextureTool : EditorWindow { private GameObject targetPrefab; private string materialOutputPath = "Assets/DuplicatedMaterials"; private string textureOutputPath = "Assets/DuplicatedTextures"; private Dictionary> duplicateMaterialMap = new Dictionary>(); private Dictionary> duplicateTextureMap = new Dictionary>(); private Dictionary materialCopies = new Dictionary(); private Dictionary textureCopies = new Dictionary(); private HashSet collectedMaterials = new HashSet(); private HashSet collectedTextures = new HashSet(); private Vector2 scroll; [MenuItem("Tools/Material & Texture Tool")] public static void ShowWindow() { var window = GetWindow("MatTex Tool"); window.minSize = new Vector2(600, 500); } private void OnGUI() { GUILayout.Label("\uD83C\uDF1F 머테리얼 & 텍스처 유틸리티", EditorStyles.boldLabel); targetPrefab = (GameObject)EditorGUILayout.ObjectField("\uD83D\uDCE6 타겟 프리팹", targetPrefab, typeof(GameObject), true); materialOutputPath = EditorGUILayout.TextField("\uD83D\uDCC2 머테리얼 저장 경로", materialOutputPath); textureOutputPath = EditorGUILayout.TextField("\uD83D\uDCC2 텍스처 저장 경로", textureOutputPath); if (GUILayout.Button("\uD83D\uDCCB 중복 이름 검사")) CollectDuplicates(); if (GUILayout.Button("\uD83D\uDD04 중복 이름 자동 변경")) RenameDuplicateAssets(); if (GUILayout.Button("\uD83D\uDD04 머테리얼 및 텍스처 복사")) DuplicateMaterialsAndTextures(); scroll = EditorGUILayout.BeginScrollView(scroll); GUILayout.Space(10); GUILayout.Label("\u26A0\uFE0F 중복된 이름의 머테리얼", EditorStyles.boldLabel); DrawDuplicateList(duplicateMaterialMap); GUILayout.Space(10); GUILayout.Label("\u26A0\uFE0F 중복된 이름의 텍스처", EditorStyles.boldLabel); DrawDuplicateList(duplicateTextureMap); GUILayout.Space(10); GUILayout.Label("\uD83D\uDD0D 참조된 모든 머테리얼", EditorStyles.boldLabel); foreach (var mat in collectedMaterials) { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button(mat.name, GUILayout.Width(200))) { EditorGUIUtility.PingObject(mat); } EditorGUILayout.ObjectField(mat, typeof(Material), false); EditorGUILayout.EndHorizontal(); } GUILayout.Space(10); GUILayout.Label("\uD83D\uDD0D 참조된 모든 텍스처", EditorStyles.boldLabel); foreach (var tex in collectedTextures) { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button(tex.name, GUILayout.Width(200))) { EditorGUIUtility.PingObject(tex); } EditorGUILayout.ObjectField(tex, typeof(Texture), false); EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } void CollectDuplicates() { duplicateMaterialMap.Clear(); duplicateTextureMap.Clear(); collectedMaterials.Clear(); collectedTextures.Clear(); if (targetPrefab == null) return; HashSet seenMaterials = new HashSet(); HashSet seenTextures = new HashSet(); var renderers = targetPrefab.GetComponentsInChildren(true); foreach (var renderer in renderers) { foreach (var mat in renderer.sharedMaterials) { if (mat == null || seenMaterials.Contains(mat)) continue; seenMaterials.Add(mat); collectedMaterials.Add(mat); if (!duplicateMaterialMap.ContainsKey(mat.name)) duplicateMaterialMap[mat.name] = new List(); duplicateMaterialMap[mat.name].Add(mat); Shader shader = mat.shader; int count = ShaderUtil.GetPropertyCount(shader); for (int i = 0; i < count; i++) { string propName = ShaderUtil.GetPropertyName(shader, i); Texture tex = mat.GetTexture(propName); if (tex == null || seenTextures.Contains(tex)) continue; seenTextures.Add(tex); collectedTextures.Add(tex); if (!duplicateTextureMap.ContainsKey(tex.name)) duplicateTextureMap[tex.name] = new List(); duplicateTextureMap[tex.name].Add(tex); } } } } void DrawDuplicateList(Dictionary> map) where T : Object { foreach (var pair in map) { if (pair.Value.Count < 2) continue; GUILayout.Label("\u26A0\uFE0F " + pair.Key + " (" + pair.Value.Count + "개)", GetRedStyle()); foreach (var obj in pair.Value) { EditorGUILayout.ObjectField(obj, typeof(T), false); } } } void RenameDuplicateAssets() { Dictionary renameCount = new Dictionary(); RenameAssetGroup(duplicateMaterialMap, ".mat", renameCount); RenameAssetGroup(duplicateTextureMap, null, renameCount); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } void RenameAssetGroup(Dictionary> map, string extOverride, Dictionary counter) where T : Object { foreach (var pair in map) { if (pair.Value.Count < 2) continue; foreach (var obj in pair.Value) { string path = AssetDatabase.GetAssetPath(obj); if (string.IsNullOrEmpty(path)) continue; if (!counter.ContainsKey(pair.Key)) counter[pair.Key] = 1; else counter[pair.Key] += 1; string newName = pair.Key + "_" + counter[pair.Key]; string result = AssetDatabase.RenameAsset(path, newName); if (result != "") { Debug.LogWarning("리네이밍 실패: " + result); } } } } void DuplicateMaterialsAndTextures() { if (targetPrefab == null) return; materialCopies.Clear(); textureCopies.Clear(); if (!AssetDatabase.IsValidFolder(materialOutputPath)) AssetDatabase.CreateFolder("Assets", "DuplicatedMaterials"); if (!AssetDatabase.IsValidFolder(textureOutputPath)) AssetDatabase.CreateFolder("Assets", "DuplicatedTextures"); Renderer[] renderers = targetPrefab.GetComponentsInChildren(true); foreach (var renderer in renderers) { Material[] newMats = new Material[renderer.sharedMaterials.Length]; for (int i = 0; i < newMats.Length; i++) { Material orig = renderer.sharedMaterials[i]; if (orig == null) continue; if (!materialCopies.ContainsKey(orig)) { Material newMat = new Material(orig); string matPath = AssetDatabase.GenerateUniqueAssetPath(materialOutputPath + "/" + orig.name + "_Copy.mat"); AssetDatabase.CreateAsset(newMat, matPath); materialCopies[orig] = newMat; CopyTextures(orig, newMat); EditorUtility.SetDirty(newMat); } newMats[i] = materialCopies[orig]; } Undo.RecordObject(renderer, "Apply Copied Materials"); renderer.sharedMaterials = newMats; } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } void CopyTextures(Material original, Material copy) { Shader shader = original.shader; int count = ShaderUtil.GetPropertyCount(shader); string[] maskProps = { "_MaskMap", "_OcclusionMap", "_DetailMask", "_RoughnessMap", "_MetallicGlossMap" }; for (int i = 0; i < count; i++) { string prop = ShaderUtil.GetPropertyName(shader, i); Texture tex = original.GetTexture(prop); if (tex == null) continue; bool isTexEnv = ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv; bool isMask = System.Array.IndexOf(maskProps, prop) >= 0; if (isTexEnv || isMask) { if (!textureCopies.ContainsKey(tex)) { string path = AssetDatabase.GetAssetPath(tex); string newPath = AssetDatabase.GenerateUniqueAssetPath(textureOutputPath + "/" + tex.name + "_Copy" + Path.GetExtension(path)); AssetDatabase.CopyAsset(path, newPath); Texture newTex = AssetDatabase.LoadAssetAtPath(newPath); textureCopies[tex] = newTex; } copy.SetTexture(prop, textureCopies[tex]); EditorUtility.SetDirty(copy); } } } GUIStyle GetRedStyle() { var style = new GUIStyle(EditorStyles.label); style.normal.textColor = Color.red; return style; } }