diff --git a/Assets/Scripts/YAMO_Scripts.meta b/Assets/Scripts/YAMO_Scripts.meta new file mode 100644 index 00000000..a9db8668 --- /dev/null +++ b/Assets/Scripts/YAMO_Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ef4c11e718c403342851dfa313db61cc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/YAMO_Scripts/FindUnusedBones.cs b/Assets/Scripts/YAMO_Scripts/FindUnusedBones.cs new file mode 100644 index 00000000..a53fa1a7 --- /dev/null +++ b/Assets/Scripts/YAMO_Scripts/FindUnusedBones.cs @@ -0,0 +1,109 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; + +public class FindUnusedBones : EditorWindow +{ + private List excludeStrings = new List(); // 제외할 문자열 리스트 + + [MenuItem("Tools/Find Unused Bones")] + public static void ShowWindow() + { + GetWindow("Find Unused Bones"); + } + + private void OnGUI() + { + GUILayout.Label("Exclude Strings", EditorStyles.boldLabel); + + // 동적으로 문자열 입력 필드와 + - 버튼을 추가 + for (int i = 0; i < excludeStrings.Count; i++) + { + GUILayout.BeginHorizontal(); + excludeStrings[i] = EditorGUILayout.TextField($"Exclude String {i + 1}", excludeStrings[i]); + + if (GUILayout.Button("-", GUILayout.Width(20))) + { + excludeStrings.RemoveAt(i); + } + GUILayout.EndHorizontal(); + } + + if (GUILayout.Button("+", GUILayout.Width(20))) + { + excludeStrings.Add(string.Empty); // 빈 문자열 입력 필드 추가 + } + + GUILayout.Space(20); + + if (GUILayout.Button("Find and Select Unused Bones")) + { + FindAndSelectUnusedBones(); + } + } + + private void FindAndSelectUnusedBones() + { + if (Selection.activeGameObject == null) + { + Debug.LogWarning("No GameObject selected!"); + return; + } + + Transform root = Selection.activeGameObject.transform; + SkinnedMeshRenderer[] skinnedMeshRenderers = root.GetComponentsInChildren(); + + HashSet usedBones = new HashSet(); + HashSet excludedObjects = new HashSet(); + + // Add all bones used by SkinnedMeshRenderers to usedBones set + foreach (var skinnedMeshRenderer in skinnedMeshRenderers) + { + foreach (var bone in skinnedMeshRenderer.bones) + { + usedBones.Add(bone); + } + // Add the SkinnedMeshRenderer's gameObject and its parents to excludedObjects set + Transform current = skinnedMeshRenderer.transform; + while (current != null) + { + excludedObjects.Add(current); + current = current.parent; + } + } + + List unusedBones = new List(); + Transform[] allBones = root.GetComponentsInChildren(); + foreach (var bone in allBones) + { + // 문자열 필터링: 사용자가 입력한 문자열을 포함한 오브젝트는 제외 + bool excludeBone = false; + foreach (var excludeString in excludeStrings) + { + if (!string.IsNullOrEmpty(excludeString) && bone.name.ToLower().Contains(excludeString.ToLower())) + { + excludeBone = true; + break; + } + } + + if (!usedBones.Contains(bone) && + !excludedObjects.Contains(bone) && + bone != root && + !excludeBone) // 필터링된 오브젝트 제외 + { + unusedBones.Add(bone); + } + } + + if (unusedBones.Count > 0) + { + Selection.objects = unusedBones.ConvertAll(b => b.gameObject).ToArray(); + Debug.Log($"Found {unusedBones.Count} unused bones. Selected in the hierarchy."); + } + else + { + Debug.Log("No unused bones found."); + } + } +} diff --git a/Assets/Scripts/YAMO_Scripts/FindUnusedBones.cs.meta b/Assets/Scripts/YAMO_Scripts/FindUnusedBones.cs.meta new file mode 100644 index 00000000..6e1b94ba --- /dev/null +++ b/Assets/Scripts/YAMO_Scripts/FindUnusedBones.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5e394acc6b38d9e4f8fadd8b5f7622cc \ No newline at end of file diff --git a/Assets/Scripts/YAMO_Scripts/MaterialAndTextureCollectorWindow.cs b/Assets/Scripts/YAMO_Scripts/MaterialAndTextureCollectorWindow.cs new file mode 100644 index 00000000..a16150c1 --- /dev/null +++ b/Assets/Scripts/YAMO_Scripts/MaterialAndTextureCollectorWindow.cs @@ -0,0 +1,248 @@ +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; + } +} diff --git a/Assets/Scripts/YAMO_Scripts/MaterialAndTextureCollectorWindow.cs.meta b/Assets/Scripts/YAMO_Scripts/MaterialAndTextureCollectorWindow.cs.meta new file mode 100644 index 00000000..ef2bd0fc --- /dev/null +++ b/Assets/Scripts/YAMO_Scripts/MaterialAndTextureCollectorWindow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3d0354ae398917c4d9e1ca82cf113f94 \ No newline at end of file diff --git a/Assets/Scripts/YAMO_Scripts/RenameHumanoidBones.cs b/Assets/Scripts/YAMO_Scripts/RenameHumanoidBones.cs new file mode 100644 index 00000000..ef006f7f --- /dev/null +++ b/Assets/Scripts/YAMO_Scripts/RenameHumanoidBones.cs @@ -0,0 +1,127 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; + +public class RenameHumanoidBones : EditorWindow +{ + private Animator selectedAnimator; + private Dictionary humanoidBones = new Dictionary(); + private Dictionary newBoneNames = new Dictionary(); + + [MenuItem("Tools/Humanoid Bone Renamer")] + public static void ShowWindow() + { + GetWindow("Humanoid Bone Renamer"); + } + + private Vector2 scrollPosition; + + private void OnGUI() + { + GUILayout.Label("Humanoid Bone Renamer", EditorStyles.boldLabel); + + if (GUILayout.Button("Find Humanoid Bones")) + { + FindHumanoidBones(); + } + + if (selectedAnimator == null) + { + GUILayout.Label("No valid Humanoid Animator selected.", EditorStyles.helpBox); + return; + } + + GUILayout.Label("Selected GameObject: " + selectedAnimator.gameObject.name, EditorStyles.boldLabel); + + if (humanoidBones.Count > 0) + { + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(400)); + + EditorGUILayout.BeginVertical("box"); + GUILayout.Label("Humanoid Bones", EditorStyles.boldLabel); + + foreach (var bone in humanoidBones) + { + EditorGUILayout.BeginHorizontal(); + + // First column: Unity's Humanoid bone name + GUILayout.Label(bone.Key.ToString(), GUILayout.Width(150)); + + // Second column: Current bone name + GUILayout.Label(bone.Value != null ? bone.Value.name : "None", GUILayout.Width(150)); + + // Third column: Input field for new name (defaulting to the first column's name) + if (!newBoneNames.ContainsKey(bone.Key)) + { + newBoneNames[bone.Key] = bone.Key.ToString(); + } + newBoneNames[bone.Key] = EditorGUILayout.TextField(newBoneNames[bone.Key], GUILayout.Width(150)); + + EditorGUILayout.EndHorizontal(); + } + + EditorGUILayout.EndVertical(); + EditorGUILayout.EndScrollView(); + } + + if (GUILayout.Button("Change Humanoid Bones Name")) + { + ChangeHumanoidBoneNames(); + } + } + + private void FindHumanoidBones() + { + selectedAnimator = null; + humanoidBones.Clear(); + newBoneNames.Clear(); + + if (Selection.activeGameObject == null) + { + Debug.LogError("No GameObject selected. Please select a GameObject with an Animator component."); + return; + } + + selectedAnimator = Selection.activeGameObject.GetComponent(); + + if (selectedAnimator == null || selectedAnimator.avatar == null || !selectedAnimator.avatar.isValid || !selectedAnimator.avatar.isHuman) + { + Debug.LogError("Selected GameObject does not have a valid Humanoid Avatar."); + return; + } + + for (int i = 0; i < HumanTrait.BoneCount; i++) + { + HumanBodyBones bone = (HumanBodyBones)i; + Transform boneTransform = selectedAnimator.GetBoneTransform(bone); + + if (boneTransform != null) + { + humanoidBones[bone] = boneTransform; + } + } + + Debug.Log("Humanoid bones found and ready for renaming."); + } + + private void ChangeHumanoidBoneNames() + { + if (selectedAnimator == null) + { + Debug.LogError("No valid Humanoid Animator selected."); + return; + } + + foreach (var bone in humanoidBones) + { + if (bone.Value != null && !string.IsNullOrWhiteSpace(newBoneNames[bone.Key])) + { + string newName = newBoneNames[bone.Key]; + Debug.Log($"Renaming {bone.Value.name} to {newName}"); + bone.Value.name = newName; + } + } + + Debug.Log("Bone renaming completed."); + } +} diff --git a/Assets/Scripts/YAMO_Scripts/RenameHumanoidBones.cs.meta b/Assets/Scripts/YAMO_Scripts/RenameHumanoidBones.cs.meta new file mode 100644 index 00000000..6b043396 --- /dev/null +++ b/Assets/Scripts/YAMO_Scripts/RenameHumanoidBones.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d90295d3945afb04dbf6709ab5c26a3e \ No newline at end of file