using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using System.Linq; using System.Security.AccessControl; using Unity.VisualScripting; using UnityEngine.UIElements; public class GameObjectUtil : EditorWindow { /// /// 선택된 오브젝트 배열 /// GameObject[] selectObjects; /// /// 선택된 오브젝트 (첫번째) /// GameObject selectObject; // string[] selectObjectNames; /// /// 콜라이더 사용여부 /// bool useCollider = true; /// /// combine시에 라이트맵 생성 여부 /// bool generateLightmapUV = true; /// /// 메트리얼 없는 오브젝트 삭제 여부 /// bool removeNoMaterialObject = true; /// /// split 이후 원본 오브젝트 삭제여부 /// bool removeOriginal = true; /// /// Combine 이후 원본 오브젝트 삭제여부 /// bool removeOriginalCombine = true; /// /// Ungroup 후 대상 오브젝트 삭제여부 /// bool removeUngroupMe = true; /// /// 삭제 대신 Hide 시킬지 여부 /// bool hide = false; /// /// 선택된 오브젝트 버텍스 개수 /// int vertexCount; /// /// 최대 버텍스 갯수가 넘어가지 않도록 선택 /// bool SelectLessThanMaximumNumber = true; string newGameObjectName = "NewGameObject"; static string ASSET_FOLDER = "Assets"; DeleteList deleteList; static int MAX_TRIANGLE_COUNT = 65000; static int SINGLE_TRIANGLE_COUNT = 50000; [MenuItem("MaprexUtil/MergeUtil")] static void Init() { GameObjectUtil window = (GameObjectUtil)EditorWindow.GetWindow(typeof(GameObjectUtil)); window.Show(); } void OnEnable() { // EditorApplication.hierarchyChanged += OnHierarchyChanged; if (deleteList == null) { deleteList = new DeleteList(); } Undo.undoRedoPerformed += UndoRedoCallback; } private void UndoRedoCallback() { for (int i = deleteList.Count - 1; i >= 0; i--) { GameObject go = GameObject.Find(deleteList.objNames[i]); if (go != null) { deleteList.RemoveAt(i); } } } bool oldEnabled; void OnGUI() { selectObjects = Selection.gameObjects; vertexCount = GetMeshCount(selectObjects); GUILayout.Label("Split", EditorStyles.boldLabel); // myString = EditorGUILayout.TextField("", myString); if (selectObjects != null && selectObjects.Length > 0) { GUI.enabled = true; selectObject = selectObjects[0]; // selectObjectNames = new string[selectObjects.Length]; // for (int i = 0; i < selectObjects.Length; i++) // { // selectObjectNames[i] = selectObjects[i].name; // } } else { GUI.enabled = false; selectObject = null; // selectObjectNames = null; } // 멀티 메트리얼 각 메트리얼 별로 메쉬 분리 useCollider = GUILayout.Toggle(useCollider, "Use Collider"); removeNoMaterialObject = GUILayout.Toggle(removeNoMaterialObject, "Remove No Material Object"); removeOriginal = GUILayout.Toggle(removeOriginal, "Remove Original Object"); // 선택된 오브젝트 서브메쉬 분리 if (GUILayout.Button("Split SubMesh")) { GameObject[] objs = selectObject.SplitSubMesh(useCollider, removeNoMaterialObject); foreach (var obj in objs) { obj.AddComponent(); } Debug.LogFormat("Split SubMesh: {0}", selectObject.name); if (removeOriginal) { Undo.DestroyObjectImmediate(selectObject); selectObject = null; } Selection.objects = objs; } // 선택된 오브젝트 포함 자식 오브젝트 서브메쉬 분리 if (GUILayout.Button("Split SubMesh All(children)")) { MeshRenderer[] meshRenderers = selectObject.GetComponentsInChildren(); if (meshRenderers != null) { for (int i = meshRenderers.Length - 1; i >= 0; i--) { if (meshRenderers[i].sharedMaterials != null && meshRenderers[i].sharedMaterials.Length > 1) { GameObject[] objs = meshRenderers[i].gameObject.SplitSubMesh(useCollider, removeNoMaterialObject); Debug.LogFormat("Split SubMesh: {0}", meshRenderers[i].gameObject.name); if (objs != null) { foreach (var obj in objs) { obj.AddComponent(); } } if (removeOriginal) { Undo.DestroyObjectImmediate(meshRenderers[i].gameObject); } } } } } oldEnabled = GUI.enabled; GUI.enabled = true; if (GUILayout.Button("Split Mesh Save All")) { string folder = ShowSaveFolderPanel(); CustomMesh[] customMeshes = FindObjectsByType(FindObjectsSortMode.InstanceID); if (customMeshes != null && customMeshes.Length > 0) { for (int i = 0; i < customMeshes.Length; i++) { Mesh mesh = SaveMesh(customMeshes[i].Mf, folder, customMeshes[i].name); if (mesh == null) { if (EditorUtility.DisplayDialog("error", string.Format("{0} 저장 실패. ", customMeshes[i].name), "확인")) { break; } } } } } GUI.enabled = oldEnabled; // 동일한 메트리얼 가지고 있는 모든 오브젝트 선택하기(부모오브젝트에서 찾음) // 선택된 오브젝트는 부모를 가지고 있어야함 GUILayout.Space(10); GUILayout.Label("Select objects same material", EditorStyles.boldLabel); GUILayout.Label(string.Format("Mesh Vertex Count: {0}", vertexCount)); EditorGUILayout.BeginHorizontal(); SelectLessThanMaximumNumber = GUILayout.Toggle(SelectLessThanMaximumNumber, "Select less than the maximum number"); if (GUILayout.Button("A. Select objects")) { GameObject[] gameObjects = SelectObjectAll(selectObject, vertexCount, SelectLessThanMaximumNumber); Selection.objects = gameObjects; } EditorGUILayout.EndHorizontal(); if (GUILayout.Button("SelectObjects and Group All")) { int count = 0; SelectObjectAndGroupAll(selectObject, vertexCount, SelectLessThanMaximumNumber, generateLightmapUV, removeOriginalCombine, ref count); } EditorGUILayout.BeginHorizontal(); newGameObjectName = GUILayout.TextField(newGameObjectName); // 빈 게임 오브젝트 생성 후 선택된 오브젝트를 해당 오브젝트 자식으로 이동 if (GUILayout.Button("B. Create Empty GameObject And Move Children")) { GameObject go = CreateEmptyGameObjectAndMoveChildren(selectObjects, newGameObjectName); Selection.activeObject = go; selectObject = go; } EditorGUILayout.EndHorizontal(); generateLightmapUV = GUILayout.Toggle(generateLightmapUV, "Generate Lightmap UV"); removeOriginalCombine = GUILayout.Toggle(removeOriginalCombine, "Remove Orignal Object"); if (GUILayout.Button("C. Combine Object")) { GameObject go = CombineObject(selectObject, generateLightmapUV, removeOriginalCombine); Selection.activeObject = go; selectObject = go; } if (GUILayout.Button("RUN A->B->C")) { GameObject[] objs = SelectObjectAll(selectObject, vertexCount, SelectLessThanMaximumNumber); if (objs != null && objs.Length > 1) { GameObject go = CreateEmptyGameObjectAndMoveChildren(objs, newGameObjectName); go = CombineObject(go, generateLightmapUV, removeOriginalCombine); Selection.activeObject = go; selectObject = go; } } oldEnabled = GUI.enabled; GUI.enabled = true; if (GUILayout.Button("Save Mesh All")) { string folder = ShowSaveFolderPanel(); SaveCombineMeshAll(folder); } GUILayout.Space(10); GUILayout.Label("Delete Object List", EditorStyles.boldLabel); GUILayout.Label(string.Format("Delete Object Count: {0}", deleteList.Count)); hide = GUILayout.Toggle(hide, "hide"); GUILayout.BeginHorizontal(); if (GUILayout.Button("Clear Delete Object List")) { deleteList.Clear(); } if (GUILayout.Button("Load List")) { string file = EditorUtility.OpenFilePanel("Select Delete List", Application.dataPath, "dlst"); string txt = File.ReadAllText(file); deleteList = ZinSerializerForXML.Deserialization(txt); if (deleteList != null) { EditorUtility.DisplayDialog("Info", string.Format("{0}\r\n불러오기 완료", file), "확인"); } else { EditorUtility.DisplayDialog("Error", string.Format("{0}\r\n불러오기 실패", file), "확인"); } } if (GUILayout.Button("Save List")) { string file = EditorUtility.SaveFilePanel("Save Delete List", Application.dataPath, "deleteList", "dlst"); if (ZinSerializerForXML.Serialization(deleteList, file)) { EditorUtility.DisplayDialog("Info", string.Format("{0}\r\n저장 완료", file), "확인"); } else { EditorUtility.DisplayDialog("Error", string.Format("{0}\r\n저장 실패", file), "확인"); } } GUILayout.EndHorizontal(); GUI.enabled = oldEnabled; if (GUILayout.Button("Delete Object")) { for (int i = 0; i < selectObjects.Length; i++) { GameObject obj = selectObjects[i]; if (hide) { Undo.RecordObject(obj, "Hide Object"); obj.SetActive(false); deleteList.Add(selectObjects[i].name); } else { Undo.DestroyObjectImmediate(obj); } } } if (GUILayout.Button("Delete Objects from DeleteList")) { Transform[] objs = selectObject.GetComponentsInChildren(); int count = 0; for (int i = 0; i < objs.Length; i++) { if (deleteList.Contains(objs[i].gameObject.name)) { GameObject obj = objs[i].gameObject; Debug.Log(string.Format("Delete Object: {0}", obj)); if (hide) { Undo.RecordObject(obj, "Hide Object"); obj.SetActive(false); count++; } else { Undo.DestroyObjectImmediate(obj); } DeleteObject(objs[i].gameObject, hide); } } Debug.Log(string.Format("Delete Object Count: {0}/{1}", count, objs.Length)); } GUILayout.Space(10); GUILayout.Label("Etc", EditorStyles.boldLabel); GUILayout.Label(string.Format("UnGroup", deleteList.Count)); GUILayout.BeginHorizontal(); removeUngroupMe = GUILayout.Toggle(removeUngroupMe, "Remove Empty Object"); if (GUILayout.Button("UnGroup")) { UnGroup(selectObject, removeUngroupMe); } GUILayout.EndHorizontal(); if (GUILayout.Button("Delete Empty Object")) { RemoveEmptyObject(selectObject); } } void DeleteObject(GameObject obj, bool isHide) { if (isHide) { Undo.RecordObject(obj, "Hide Object"); obj.SetActive(false); } else { Undo.DestroyObjectImmediate(obj); } } string ShowSaveFolderPanel() { string folder = EditorUtility.SaveFolderPanel("Select Save Folder", Application.dataPath, ""); if (folder.Contains(ASSET_FOLDER)) { string[] split = folder.Split(ASSET_FOLDER); return ASSET_FOLDER + split[1]; } return null; } void SaveCombineMeshAll(string assetFilePath) { SimpleMeshCombine[] simpleMeshs = FindObjectsByType(FindObjectsSortMode.InstanceID); if (simpleMeshs != null && simpleMeshs.Length > 0) { Debug.LogFormat("Total Combine Mesh: {0}", simpleMeshs.Length); for (int i = 0; i < simpleMeshs.Length; i++) { MeshFilter mf = simpleMeshs[i].combined.GetComponent(); Mesh mesh = SaveMesh(mf, simpleMeshs[i].name, assetFilePath); simpleMeshs[i].combined.GetComponent().sharedMesh = mesh; simpleMeshs[i].autoOverwrite = mesh; } } } /// /// 이름에 경로 구분자가 들어가는 경우 처리 /// /// /// string ReplaceName(string name) { // 이름에 경로구분자가 들어가는 경우 처리 if (name.Contains("\\")) { name = name.Replace("\\", ""); } else if (name.Contains("/")) { name = name.Replace("/", ""); } return name; } Mesh SaveMesh(MeshFilter mf, string folder, string name) { name = ReplaceName(name); string path = folder + "/" + name + ".asset"; if (mf.sharedMesh != null) { UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath(path, (Type)typeof(object)); if (asset == null) { Debug.Log(path); AssetDatabase.CreateAsset(mf.sharedMesh, path); Mesh loadMesh = (Mesh)AssetDatabase.LoadAssetAtPath(path, (Type)typeof(object)); Debug.Log(loadMesh); mf.GetComponent().sharedMesh = loadMesh; Debug.Log("Saved mesh asset: " + path); return loadMesh; } else { Debug.LogWarningFormat("{0} file exists.", name); return mf.sharedMesh; } } else { Debug.LogErrorFormat("Save Mesh faild. {0}, {1}", mf.name, path); return null; } } int GetMeshCount(GameObject[] gos) { int count = 0; for (int i = 0; i < gos.Length; i++) { count += gos[i].GetMeshCount(); } return count; } GameObject CombineObject(GameObject go, bool generateLightmapUV, bool removeOriginal) { SimpleMeshCombine simpleMesh = go.GetComponent(); if (simpleMesh == null) simpleMesh = go.AddComponent(); simpleMesh.generateLightmapUV = generateLightmapUV; try { simpleMesh.CombineMeshes(); } catch (System.Exception ex) { Debug.LogError(ex.Message); return null; } // Debug.Log(combineObj); if (removeOriginal) { MeshRenderer[] objs = simpleMesh.GetComponentsInChildren(); for (int i = objs.Length - 1; i >= 0; i--) { if (!objs[i].enabled) Undo.DestroyObjectImmediate(objs[i].gameObject); } } return simpleMesh.gameObject; } GameObject CreateEmptyGameObjectAndMoveChildren(GameObject[] objs, string name) { GameObject go = new GameObject(); go.name = name; go.transform.localScale = objs[0].transform.parent.localScale; for (int i = 0; i < objs.Length; i++) { Debug.Log(objs[i]); GameObject newObj = Instantiate(objs[i]); newObj.transform.SetParent(go.transform); newObj.transform.position = objs[i].transform.position; newObj.transform.rotation = objs[i].transform.rotation; newObj.transform.localScale = objs[i].transform.localScale; newObj.name = objs[i].name; Undo.DestroyObjectImmediate(objs[i]); } return go; } void SetNull(Transform tr, int no) { tr.SetParent(null); tr.name = tr.name + "_" + no.ToString(); } void SelectObjectAndGroupAll(GameObject selObj, int vertexCount, bool selectLessThanMaximumNumber, bool generateLightmapUV, bool removeOriginal, ref int count) { count++; Transform[] children = selObj.GetComponentsInChildren(); if (children.Length == 1) return; Transform tr = children[1]; if (children.Length == 2) { SetNull(tr, count); return; } Debug.Log("Select: " + tr.name); if (tr.gameObject.GetMeshCount() >= SINGLE_TRIANGLE_COUNT) { SetNull(tr, count); SelectObjectAndGroupAll(selObj, vertexCount, selectLessThanMaximumNumber, generateLightmapUV, removeOriginal, ref count); } GameObject[] objs = SelectObjectAll(tr.gameObject, vertexCount, selectLessThanMaximumNumber); if (objs != null) { if (objs.Length > 1) { GameObject go = CreateEmptyGameObjectAndMoveChildren(objs, "combined_" + count.ToString()); if (go != null) { go = CombineObject(go, generateLightmapUV, removeOriginal); if (go == null) { return; } } else { return; } } // 선택된 오브젝트가 1개일때 else { SetNull(tr, count); } } else { return; } SelectObjectAndGroupAll(selObj, vertexCount, selectLessThanMaximumNumber, generateLightmapUV, removeOriginal, ref count); } GameObject[] SelectObjectAll(GameObject selectObject, int vertexCount, bool selectLessThanMaximumNumber) { if (selectObject == null) { return null; } MeshRenderer ren = selectObject.GetComponent(); bool run = true; if (ren == null) { Debug.LogError("Mesh Renderer not found."); run = false; } else if (ren.sharedMaterials == null || ren.sharedMaterials.Length == 0) { Debug.LogError("Materials is null"); run = false; } else if (ren.sharedMaterials.Length > 1) { Debug.LogError("sub mesh가 2개 이상인 오브젝트는 선택할 수 없습니다."); run = false; } List gameObjects = new List(); if (run) { vertexCount = 0; GameObject parent = ren.transform.parent.gameObject; Debug.Log(ren.transform.parent); MeshRenderer[] renderers = parent.GetComponentsInChildren(); for (int i = 0; i < renderers.Length; i++) { if (renderers[i].sharedMaterials.Length == 1 && renderers[i].sharedMaterial == ren.sharedMaterial) { int myMeshCount = renderers[i].gameObject.GetMeshCount(); if (myMeshCount >= SINGLE_TRIANGLE_COUNT) continue; if (!renderers[i].gameObject.activeSelf) continue; int currentMeshCount = myMeshCount + vertexCount; if (gameObjects.Count <= 1 && MAX_TRIANGLE_COUNT <= currentMeshCount) continue; if (selectLessThanMaximumNumber) { if (currentMeshCount <= MAX_TRIANGLE_COUNT) { gameObjects.Add(renderers[i].gameObject); } else { break; } } else { gameObjects.Add(renderers[i].gameObject); } vertexCount = currentMeshCount; } } return gameObjects.ToArray(); } gameObjects.Add(selectObject); return gameObjects.ToArray(); } /// /// 대상 오브젝트 UnGroup /// 대상 오브젝트의 모든 자식오브젝트를 대상 오브젝트와 동일한 level로 변경 /// /// 대상 오브젝트 /// 빈 오브젝트 삭제여부 void UnGroup(GameObject go, bool removeMe) { PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType(go); if (prefabAssetType == PrefabAssetType.NotAPrefab) { Transform me = go.transform; Transform[] children = go.GetComponentsInChildren(); for (int i = 0; i < children.Length; i++) { if (children[i] != me) { children[i].SetParent(me); } } if (removeMe) { RemoveEmptyObject(go); } } else { EditorUtility.DisplayDialog("error", "프리팹은 대상이 될 수 없습니다.", "확인"); } } void RemoveEmptyObject(GameObject go) { Transform[] transforms = go.GetComponentsInChildren(); if (transforms != null) { for (int i = transforms.Length - 1; i >= 0; i--) { if (transforms[i].childCount == 0) { Component[] components = transforms[i].GetComponents(); if (components.Length == 1) { Undo.DestroyObjectImmediate(transforms[i].gameObject); Debug.LogFormat("Remove: {0}", go.name); } } } } } }