Initial Unity project commit
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
|
||||
namespace Test_2022.Utils
|
||||
{
|
||||
public class HierarchyCleaner : EditorWindow
|
||||
{
|
||||
private static bool removeRecursively = false;
|
||||
|
||||
[MenuItem("Tools/Hierarchy/Remove Name Suffixes (e.g. \" (1)\")")]
|
||||
private static void CleanSelectedChildren()
|
||||
{
|
||||
GameObject[] selectedObjects = Selection.gameObjects;
|
||||
|
||||
if (selectedObjects.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先选择一个父对象!", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
Regex regex = new Regex(@"\s\(\d+\)$");
|
||||
|
||||
Undo.RecordObjects(selectedObjects, "Clean Names");
|
||||
|
||||
int count = 0;
|
||||
foreach (GameObject parent in selectedObjects)
|
||||
{
|
||||
count += CleanChildrenNames(parent.transform, regex);
|
||||
}
|
||||
|
||||
Debug.Log($"已清理 {count} 个对象的名称后缀。");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Hierarchy/Open Cleaner Window...")]
|
||||
private static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<HierarchyCleaner>("Hierarchy清理工具");
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Hierarchy 名称清理工具", EditorStyles.boldLabel);
|
||||
GUILayout.Space(10);
|
||||
|
||||
removeRecursively = EditorGUILayout.ToggleLeft(" 递归清理所有子层级 (Recursive)", removeRecursively);
|
||||
|
||||
GUILayout.Space(15);
|
||||
|
||||
if (GUILayout.Button("去除选中对象子物体的序号后缀", GUILayout.Height(40)))
|
||||
{
|
||||
CleanSelectedWithOptions();
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"功能说明:\n" +
|
||||
"移除对象名称末尾的 \" (1)\", \" (2)\" 等自动生成的后缀。\n" +
|
||||
"例如: \"Box (1)\" -> \"Box\"\n" +
|
||||
"注意: \"Box_1\" 或 \"Level1\" 不会受影响。",
|
||||
MessageType.Info
|
||||
);
|
||||
}
|
||||
|
||||
private static void CleanSelectedWithOptions()
|
||||
{
|
||||
GameObject[] selectedObjects = Selection.gameObjects;
|
||||
|
||||
if (selectedObjects.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先选择一个父对象!", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
Regex regex = new Regex(@"\s\(\d+\)$");
|
||||
Undo.RecordObjects(selectedObjects, "Clean Names");
|
||||
|
||||
int count = 0;
|
||||
foreach (GameObject parent in selectedObjects)
|
||||
{
|
||||
if (removeRecursively)
|
||||
{
|
||||
count += CleanChildrenNamesRecursive(parent.transform, regex);
|
||||
}
|
||||
else
|
||||
{
|
||||
count += CleanChildrenNames(parent.transform, regex);
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.DisplayDialog("完成", $"清理完成!共修改了 {count} 个对象的名称。", "确定");
|
||||
}
|
||||
|
||||
private static int CleanChildrenNames(Transform parent, Regex regex)
|
||||
{
|
||||
int modifiedCount = 0;
|
||||
|
||||
for (int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
Transform child = parent.GetChild(i);
|
||||
if (regex.IsMatch(child.name))
|
||||
{
|
||||
Undo.RecordObject(child.gameObject, "Rename Object");
|
||||
string oldName = child.name;
|
||||
child.name = regex.Replace(oldName, "");
|
||||
modifiedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedCount;
|
||||
}
|
||||
|
||||
private static int CleanChildrenNamesRecursive(Transform parent, Regex regex)
|
||||
{
|
||||
int modifiedCount = CleanChildrenNames(parent, regex);
|
||||
|
||||
for (int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
Transform child = parent.GetChild(i);
|
||||
if (child.childCount > 0)
|
||||
{
|
||||
modifiedCount += CleanChildrenNamesRecursive(child, regex);
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedCount;
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/Remove Name Suffixes (Clean)", false, 11)]
|
||||
private static void ContextMenuClean()
|
||||
{
|
||||
CleanSelectedChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 734604d4c00eec14a80c453b2d7c7e7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,180 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
|
||||
namespace Test_2022.Utils
|
||||
{
|
||||
public class HierarchySorter : EditorWindow
|
||||
{
|
||||
private static bool includeInactive = true;
|
||||
private static bool sortRecursively = false;
|
||||
|
||||
[MenuItem("Tools/Hierarchy/Sort Immediate Children Only (A-Z)")]
|
||||
private static void SortSelectedChildren()
|
||||
{
|
||||
GameObject[] selectedObjects = Selection.gameObjects;
|
||||
|
||||
if (selectedObjects.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先选择一个父对象!", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (GameObject parent in selectedObjects)
|
||||
{
|
||||
SortChildren(parent.transform);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Hierarchy/Sort All Descendants (Recursive)")]
|
||||
private static void SortSelectedChildrenRecursive()
|
||||
{
|
||||
GameObject[] selectedObjects = Selection.gameObjects;
|
||||
|
||||
if (selectedObjects.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先选择一个父对象!", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (GameObject parent in selectedObjects)
|
||||
{
|
||||
SortChildrenRecursive(parent.transform);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Hierarchy/Open Sort Window...")]
|
||||
private static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<HierarchySorter>("Hierarchy排序工具");
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Hierarchy 排序工具", EditorStyles.boldLabel);
|
||||
GUILayout.Space(10);
|
||||
|
||||
includeInactive = EditorGUILayout.ToggleLeft(" 包含非激活对象 (Include Inactive)", includeInactive);
|
||||
sortRecursively = EditorGUILayout.ToggleLeft(" 递归排序所有子层级 (Recursive)", sortRecursively);
|
||||
|
||||
GUILayout.Space(15);
|
||||
|
||||
string buttonText = sortRecursively ? "排序选中对象及其所有后代" : "仅排序选中对象的直接子级";
|
||||
|
||||
if (GUILayout.Button(buttonText, GUILayout.Height(40)))
|
||||
{
|
||||
SortSelectedWithOptions();
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
sortRecursively
|
||||
? "模式:递归\n将排序选中对象下的所有层级物体。\n结构会被彻底重新整理。"
|
||||
: "模式:仅直接子级\n只会排序第一层子对象。\n子对象的子对象保持原样(符合您的需求)。",
|
||||
MessageType.Info
|
||||
);
|
||||
}
|
||||
|
||||
private static void SortSelectedWithOptions()
|
||||
{
|
||||
GameObject[] selectedObjects = Selection.gameObjects;
|
||||
|
||||
if (selectedObjects.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先选择一个父对象!", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObjects(selectedObjects, "Sort Children");
|
||||
|
||||
int totalSorted = 0;
|
||||
foreach (GameObject parent in selectedObjects)
|
||||
{
|
||||
if (sortRecursively)
|
||||
{
|
||||
totalSorted += SortChildrenRecursive(parent.transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
totalSorted += SortChildren(parent.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int SortChildren(Transform parent)
|
||||
{
|
||||
if (parent == null) return 0;
|
||||
|
||||
var children = new System.Collections.Generic.List<Transform>();
|
||||
|
||||
for (int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
children.Add(parent.GetChild(i));
|
||||
}
|
||||
|
||||
if (!includeInactive)
|
||||
{
|
||||
children = children.Where(c => c.gameObject.activeSelf).ToList();
|
||||
}
|
||||
|
||||
if (children.Count <= 1) return 0;
|
||||
|
||||
var sortedChildren = children.OrderBy(c => c.name).ToList();
|
||||
|
||||
bool orderChanged = false;
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
if (children[i] != sortedChildren[i])
|
||||
{
|
||||
orderChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (orderChanged)
|
||||
{
|
||||
for (int i = 0; i < sortedChildren.Count; i++)
|
||||
{
|
||||
Undo.SetTransformParent(sortedChildren[i], parent, "Sort Children");
|
||||
sortedChildren[i].SetSiblingIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int SortChildrenRecursive(Transform parent)
|
||||
{
|
||||
if (parent == null) return 0;
|
||||
|
||||
int totalSorted = SortChildren(parent);
|
||||
|
||||
for (int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
Transform child = parent.GetChild(i);
|
||||
if (child.childCount > 0)
|
||||
{
|
||||
totalSorted += SortChildrenRecursive(child);
|
||||
}
|
||||
}
|
||||
|
||||
return totalSorted;
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/Sort Children Alphabetically", false, 0)]
|
||||
private static void ContextMenuSort()
|
||||
{
|
||||
SortSelectedChildren();
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/Sort Children Alphabetically (Recursive)", false, 1)]
|
||||
private static void ContextMenuSortRecursive()
|
||||
{
|
||||
SortSelectedChildrenRecursive();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c453725933b2364459e95a4fe469dfdc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,92 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
public class RuntimeDebugConsole : MonoBehaviour
|
||||
{
|
||||
private List<string> logs = new List<string>();
|
||||
private Vector2 scrollPosition;
|
||||
private bool showConsole = false;
|
||||
private GUIStyle logStyle;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Application.logMessageReceived += HandleLog;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Application.logMessageReceived -= HandleLog;
|
||||
}
|
||||
|
||||
void HandleLog(string logString, string stackTrace, LogType type)
|
||||
{
|
||||
string color = "white";
|
||||
if (type == LogType.Error || type == LogType.Exception) color = "red";
|
||||
else if (type == LogType.Warning) color = "yellow";
|
||||
|
||||
// Only store message, stack trace might be too long for small console
|
||||
logs.Add($"<color={color}>[{type}] {logString}</color>");
|
||||
|
||||
if (logs.Count > 50)
|
||||
{
|
||||
logs.RemoveAt(0);
|
||||
}
|
||||
|
||||
// Scroll to bottom
|
||||
scrollPosition.y = float.MaxValue;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Toggle console with Backquote (~) key
|
||||
if (Input.GetKeyDown(KeyCode.BackQuote))
|
||||
{
|
||||
showConsole = !showConsole;
|
||||
}
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (!showConsole) return;
|
||||
|
||||
if (logStyle == null)
|
||||
{
|
||||
logStyle = new GUIStyle(GUI.skin.label);
|
||||
logStyle.richText = true;
|
||||
logStyle.wordWrap = true;
|
||||
}
|
||||
|
||||
// Draw a semi-transparent background
|
||||
GUI.Box(new Rect(0, 0, Screen.width, Screen.height * 0.5f), "Debug Console (Press ` to toggle)");
|
||||
|
||||
GUILayout.BeginArea(new Rect(10, 20, Screen.width - 20, Screen.height * 0.5f - 40));
|
||||
|
||||
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
|
||||
foreach (var log in logs)
|
||||
{
|
||||
GUILayout.Label(log, logStyle);
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Clear", GUILayout.Width(100)))
|
||||
{
|
||||
logs.Clear();
|
||||
}
|
||||
if (GUILayout.Button("Close", GUILayout.Width(100)))
|
||||
{
|
||||
showConsole = false;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8c3853099b78c14f8b22534ffc67f33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user