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);
}
}
}
}
}
}