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
|
{
|
/// <summary>
|
/// 선택된 오브젝트 배열
|
/// </summary>
|
GameObject[] selectObjects;
|
|
/// <summary>
|
/// 선택된 오브젝트 (첫번째)
|
/// </summary>
|
GameObject selectObject;
|
|
// string[] selectObjectNames;
|
|
/// <summary>
|
/// 콜라이더 사용여부
|
/// </summary>
|
bool useCollider = true;
|
|
/// <summary>
|
/// combine시에 라이트맵 생성 여부
|
/// </summary>
|
bool generateLightmapUV = true;
|
|
|
/// <summary>
|
/// 메트리얼 없는 오브젝트 삭제 여부
|
/// </summary>
|
bool removeNoMaterialObject = true;
|
|
/// <summary>
|
/// split 이후 원본 오브젝트 삭제여부
|
/// </summary>
|
bool removeOriginal = true;
|
|
/// <summary>
|
/// Combine 이후 원본 오브젝트 삭제여부
|
/// </summary>
|
bool removeOriginalCombine = true;
|
|
/// <summary>
|
/// Ungroup 후 대상 오브젝트 삭제여부
|
/// </summary>
|
bool removeUngroupMe = true;
|
|
/// <summary>
|
/// 삭제 대신 Hide 시킬지 여부
|
/// </summary>
|
bool hide = false;
|
|
/// <summary>
|
/// 선택된 오브젝트 버텍스 개수
|
/// </summary>
|
int vertexCount;
|
|
/// <summary>
|
/// 최대 버텍스 갯수가 넘어가지 않도록 선택
|
/// </summary>
|
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<CustomMesh>();
|
}
|
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<MeshRenderer>();
|
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<CustomMesh>();
|
}
|
}
|
|
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<CustomMesh>(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<DeleteList>(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>(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<Transform>();
|
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<SimpleMeshCombine>(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<MeshFilter>();
|
Mesh mesh = SaveMesh(mf, simpleMeshs[i].name, assetFilePath);
|
|
simpleMeshs[i].combined.GetComponent<MeshFilter>().sharedMesh = mesh;
|
simpleMeshs[i].autoOverwrite = mesh;
|
}
|
}
|
}
|
|
/// <summary>
|
/// 이름에 경로 구분자가 들어가는 경우 처리
|
/// </summary>
|
/// <param name="name"></param>
|
/// <returns></returns>
|
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<MeshFilter>().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<SimpleMeshCombine>();
|
if (simpleMesh == null)
|
simpleMesh = go.AddComponent<SimpleMeshCombine>();
|
|
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<MeshRenderer>();
|
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<Transform>();
|
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<MeshRenderer>();
|
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<GameObject> gameObjects = new List<GameObject>();
|
|
if (run)
|
{
|
vertexCount = 0;
|
|
GameObject parent = ren.transform.parent.gameObject;
|
Debug.Log(ren.transform.parent);
|
MeshRenderer[] renderers = parent.GetComponentsInChildren<MeshRenderer>();
|
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();
|
}
|
|
/// <summary>
|
/// 대상 오브젝트 UnGroup
|
/// 대상 오브젝트의 모든 자식오브젝트를 대상 오브젝트와 동일한 level로 변경
|
/// </summary>
|
/// <param name="go">대상 오브젝트</param>
|
/// <param name="removeMe">빈 오브젝트 삭제여부</param>
|
void UnGroup(GameObject go, bool removeMe)
|
{
|
PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType(go);
|
if (prefabAssetType == PrefabAssetType.NotAPrefab)
|
{
|
Transform me = go.transform;
|
Transform[] children = go.GetComponentsInChildren<Transform>();
|
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<Transform>();
|
if (transforms != null)
|
{
|
for (int i = transforms.Length - 1; i >= 0; i--)
|
{
|
if (transforms[i].childCount == 0)
|
{
|
Component[] components = transforms[i].GetComponents<Component>();
|
if (components.Length == 1)
|
{
|
Undo.DestroyObjectImmediate(transforms[i].gameObject);
|
Debug.LogFormat("Remove: {0}", go.name);
|
}
|
}
|
}
|
}
|
}
|
}
|