Initial Unity project commit

This commit is contained in:
2026-07-08 19:53:15 +08:00
commit 75f254212e
15657 changed files with 11633879 additions and 0 deletions
@@ -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: