diff --git a/Assets/Editor/QuickSelector.CS b/Assets/Editor/QuickSelector.CS new file mode 100644 index 00000000..20484d10 --- /dev/null +++ b/Assets/Editor/QuickSelector.CS @@ -0,0 +1,231 @@ +using UnityEngine; +using UnityEditor; +using UnityEngine.UI; +using System.Collections.Generic; +using System.Linq; + +[InitializeOnLoad] +public class QuickSelectorHud +{ + public static GameObject HoveredObject; + + static QuickSelectorHud() + { + SceneView.duringSceneGui += OnSceneGUI; + } + + private static void OnSceneGUI(SceneView sceneView) + { + if (HoveredObject != null) + { + DrawHighlight(HoveredObject); + sceneView.Repaint(); + } + + Event e = Event.current; + if (e.type == EventType.MouseDown && e.button == 1 && e.shift) + { + var entries = CollectData(sceneView, e.mousePosition); + QuickSelectorPopup popup = new QuickSelectorPopup(entries); + PopupWindow.Show(new Rect(e.mousePosition.x, e.mousePosition.y, 0, 0), popup); + e.Use(); + } + } + + private static void DrawHighlight(GameObject go) + { + Handles.color = new Color(0f, 0.7f, 1f, 1f); // 亮蓝色 + RectTransform rt = go.GetComponent(); + if (rt != null) + { + Vector3[] corners = new Vector3[4]; + rt.GetWorldCorners(corners); + Handles.DrawPolyLine(corners[0], corners[1], corners[2], corners[3], corners[0]); + Handles.color = new Color(0f, 0.7f, 1f, 0.1f); + Handles.DrawAAConvexPolygon(corners); + } + else + { + Renderer r = go.GetComponent(); + if (r != null) Handles.DrawWireCube(r.bounds.center, r.bounds.size); + } + } + + private static List CollectData(SceneView sceneView, Vector2 mousePos) + { + bool enableUI = EditorPrefs.GetBool("QS_UI", true); + bool enable3D = EditorPrefs.GetBool("QS_3D", true); + Vector2 guiPos = mousePos; + guiPos.y = sceneView.camera.pixelHeight - guiPos.y; + Ray ray = HandleUtility.GUIPointToWorldRay(mousePos); + + HashSet processed = new HashSet(); + List list = new List(); + + GameObject smart = HandleUtility.PickGameObject(mousePos, false); + if (smart != null) AddEntry(list, processed, smart, "PICK"); + + if (enableUI) + { + var rects = GameObject.FindObjectsByType(FindObjectsSortMode.None); + foreach (var rect in rects) + { + if (rect.gameObject.activeInHierarchy && RectTransformUtility.RectangleContainsScreenPoint(rect, guiPos, sceneView.camera)) + AddEntry(list, processed, rect.gameObject, "UI"); + } + } + + if (enable3D) + { + RaycastHit[] hits = Physics.RaycastAll(ray, float.MaxValue); + foreach (var hit in hits) AddEntry(list, processed, hit.collider.gameObject, "3D"); + foreach (var r in GameObject.FindObjectsByType(FindObjectsSortMode.None)) + if (!processed.Contains(r.gameObject) && r.bounds.IntersectRay(ray, out _)) + AddEntry(list, processed, r.gameObject, "Mesh"); + } + return list; + } + + private static void AddEntry(List list, HashSet set, GameObject go, string src) + { + if (go == null || !set.Add(go)) return; + string maj = (go.GetComponent() != null) ? "UI" : (go.GetComponent() != null ? "3D" : "Mesh"); + string min = (maj == "UI") ? GetUIType(go) : (go.GetComponent()?.GetType().Name ?? "Object"); + list.Add(new SelectionEntry { go = go, root = GetRoot(go.transform), depth = GetDepth(go.transform), sibling = go.transform.GetSiblingIndex(), major = maj, minor = min }); + } + + private static string GetUIType(GameObject go) + { + if (go.GetComponent("TextMeshProUGUI")) return "TMPro"; + if (go.GetComponent()) return "Text"; + if (go.GetComponent()) return "Image"; + return "Rect"; + } + private static GameObject GetRoot(Transform t) { while (t.parent != null) t = t.parent; return t.gameObject; } + private static int GetDepth(Transform t) { int d = 0; while (t.parent != null) { d++; t = t.parent; } return d; } +} + +public class SelectionEntry { public GameObject go, root; public int depth, sibling; public string major, minor; } + +public class QuickSelectorPopup : PopupWindowContent +{ + private List _entries; + private List _displayItems; + private Vector2 _scroll; + private GUIStyle _hoverStyle; + private GUIStyle _richLabelStyle; // 修正点:手动创建支持富文本的 Style + + public QuickSelectorPopup(List entries) + { + _entries = entries; + RefreshList(); + } + + public override Vector2 GetWindowSize() => new Vector2(280, Mathf.Min((_displayItems.Count * 22) + 30, 450)); + + public override void OnGUI(Rect rect) + { + // 样式初始化 + if (_hoverStyle == null) + { + _hoverStyle = new GUIStyle(EditorStyles.label); + _hoverStyle.normal.background = MakeTex(2, 2, new Color(0.2f, 0.5f, 1f, 0.4f)); + } + if (_richLabelStyle == null) + { + _richLabelStyle = new GUIStyle(EditorStyles.label); + _richLabelStyle.richText = true; // 关键修正:开启富文本支持 + } + + _scroll = EditorGUILayout.BeginScrollView(_scroll); + Event e = Event.current; + + foreach (var item in _displayItems) + { + if (item.isSep) { EditorGUILayout.LabelField("", GUI.skin.horizontalSlider); continue; } + + Rect r = EditorGUILayout.GetControlRect(false, 20); + bool isHover = r.Contains(e.mousePosition); + + if (isHover) + { + GUI.Box(r, "", _hoverStyle); + if (QuickSelectorHud.HoveredObject != item.ent.go) + { + QuickSelectorHud.HoveredObject = item.ent.go; + EditorGUIUtility.PingObject(item.ent.go); + SceneView.RepaintAll(); + } + if (e.type == EventType.MouseDown && e.button == 0) + { + Selection.activeGameObject = item.ent.go; + this.editorWindow.Close(); + } + } + + // 使用修正后的富文本样式绘制 + GUI.Label(r, item.label, _richLabelStyle); + } + EditorGUILayout.EndScrollView(); + + DrawToolbar(); + + if (e.type == EventType.MouseMove && !rect.Contains(e.mousePosition)) + { + QuickSelectorHud.HoveredObject = null; + } + } + + private void DrawToolbar() + { + GUILayout.BeginHorizontal(EditorStyles.toolbar); + if (GUILayout.Toggle(EditorPrefs.GetBool("QS_UI", true), "UI", EditorStyles.toolbarButton) != EditorPrefs.GetBool("QS_UI", true)) + { + EditorPrefs.SetBool("QS_UI", !EditorPrefs.GetBool("QS_UI")); RefreshList(); + } + if (GUILayout.Toggle(EditorPrefs.GetBool("QS_3D", true), "3D", EditorStyles.toolbarButton) != EditorPrefs.GetBool("QS_3D", true)) + { + EditorPrefs.SetBool("QS_3D", !EditorPrefs.GetBool("QS_3D")); RefreshList(); + } + GUILayout.FlexibleSpace(); + if (GUILayout.Toggle(EditorPrefs.GetBool("QS_REV", false), "Reverse", EditorStyles.toolbarButton) != EditorPrefs.GetBool("QS_REV", false)) + { + EditorPrefs.SetBool("QS_REV", !EditorPrefs.GetBool("QS_REV")); RefreshList(); + } + GUILayout.EndHorizontal(); + } + + public override void OnClose() { QuickSelectorHud.HoveredObject = null; SceneView.RepaintAll(); } + + private void RefreshList() + { + _displayItems = new List(); + bool rev = EditorPrefs.GetBool("QS_REV", false); + var groups = _entries.GroupBy(x => x.root).OrderByDescending(g => g.Any(e => e.major == "UI")).ThenBy(g => g.Key.name); + bool first = true; + foreach (var g in groups) + { + if (!first) _displayItems.Add(new DisplayItem { isSep = true }); + first = false; + var sorted = rev ? g.OrderByDescending(x => x.depth).ThenByDescending(x => x.sibling) : g.OrderBy(x => x.depth).ThenBy(x => x.sibling); + int minD = g.Min(x => x.depth); + int maxD = g.Max(x => x.depth); + foreach (var ent in sorted) + { + int ind = rev ? (maxD - ent.depth) : (ent.depth - minD); + string color = (ent.major == "UI" ? "#00E6FF" : "#AAAAAA"); // 调整 UI 标签为更亮的青色 + string label = $"{new string(' ', ind * 4)}[{ent.major}|{ent.minor}] {ent.go.name}"; + _displayItems.Add(new DisplayItem { ent = ent, label = label }); + } + } + if (editorWindow != null) editorWindow.Repaint(); + } + + private Texture2D MakeTex(int w, int h, Color col) + { + Color[] pix = new Color[w * h]; for (int i = 0; i < pix.Length; i++) pix[i] = col; + Texture2D t = new Texture2D(w, h); t.SetPixels(pix); t.Apply(); return t; + } + + private class DisplayItem { public bool isSep; public SelectionEntry ent; public string label; } +} \ No newline at end of file diff --git a/Assets/Editor/QuickSelector.CS.meta b/Assets/Editor/QuickSelector.CS.meta new file mode 100644 index 00000000..99f74451 --- /dev/null +++ b/Assets/Editor/QuickSelector.CS.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd4b3a300a1a28f448297100b29f7440 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/QuickSelector.cx b/Assets/Editor/QuickSelector.cx new file mode 100644 index 00000000..b92f633c --- /dev/null +++ b/Assets/Editor/QuickSelector.cx @@ -0,0 +1,180 @@ +using UnityEngine; +using UnityEditor; +using UnityEngine.UI; +using System.Collections.Generic; +using System.Linq; + +[InitializeOnLoad] +public class QuickSelector//原生菜单 +{ + // 配置键名 + private const string PREF_UI = "QS_EnableUI"; + private const string PREF_3D = "QS_Enable3D"; + private const string PREF_REVERSE = "QS_ReverseOrder"; + + static QuickSelector() + { + SceneView.duringSceneGui += OnSceneGUI; + } + + private static void OnSceneGUI(SceneView sceneView) + { + Event e = Event.current; + + // Shift + 右键触发 + if (e.type == EventType.MouseDown && e.button == 1 && e.shift) + { + // 加载配置 + bool enableUI = EditorPrefs.GetBool(PREF_UI, true); + bool enable3D = EditorPrefs.GetBool(PREF_3D, true); + bool reverseOrder = EditorPrefs.GetBool(PREF_REVERSE, false); + + Vector2 guiPos = e.mousePosition; + guiPos.y = sceneView.camera.pixelHeight - guiPos.y; + Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition); + + HashSet processedObjects = new HashSet(); + List entries = new List(); + + // 1. 智能拾取 (PickGameObject) + GameObject smartPick = HandleUtility.PickGameObject(e.mousePosition, false); + if (smartPick != null) AddEntry(entries, processedObjects, smartPick, "PICK"); + + // 2. UI 扫描 + if (enableUI) + { + var allRects = GameObject.FindObjectsByType(FindObjectsSortMode.None); + foreach (var rect in allRects) + { + if (!rect.gameObject.activeInHierarchy) continue; + if (RectTransformUtility.RectangleContainsScreenPoint(rect, guiPos, sceneView.camera)) + AddEntry(entries, processedObjects, rect.gameObject, "UI"); + } + } + + // 3. 3D/Mesh 扫描 + if (enable3D) + { + RaycastHit[] hits = Physics.RaycastAll(ray, float.MaxValue); + foreach (var hit in hits) AddEntry(entries, processedObjects, hit.collider.gameObject, "3D"); + + Renderer[] allRenderers = GameObject.FindObjectsByType(FindObjectsSortMode.None); + foreach (var r in allRenderers) + { + if (processedObjects.Contains(r.gameObject)) continue; + if (r.bounds.IntersectRay(ray, out _)) AddEntry(entries, processedObjects, r.gameObject, "Mesh"); + } + } + + if (entries.Count > 0 || true) + { + GenericMenu menu = new GenericMenu(); + + // 根节点分组:UI组优先 + var groups = entries.GroupBy(x => x.rootObject) + .OrderByDescending(g => g.Any(ent => ent.majorType == "UI")) + .ThenBy(g => g.Key.name); + + foreach (var group in groups) + { + if (menu.GetItemCount() > 0) menu.AddSeparator(""); + + // 组内排序 + var sortedInGroup = reverseOrder + ? group.OrderByDescending(x => x.depth).ThenByDescending(x => x.siblingIndex) + : group.OrderBy(x => x.depth).ThenBy(x => x.siblingIndex); + + int minDepth = group.Min(x => x.depth); + int maxDepth = group.Max(x => x.depth); + + foreach (var entry in sortedInGroup) + { + // --- 修复点:缩进计算 --- + // 顺序:深度越深缩进越多 (entry.depth - minDepth) + // 逆序:深度越浅缩进越多 (maxDepth - entry.depth) + int indentLevel = reverseOrder ? (maxDepth - entry.depth) : (entry.depth - minDepth); + + string indent = new string(' ', indentLevel * 2); + string label = $"{indent}[ {entry.majorType} | {entry.minorType} ]: {entry.gameObject.name}"; + + menu.AddItem(new GUIContent(label), false, () => + { + Selection.activeGameObject = entry.gameObject; + EditorGUIUtility.PingObject(entry.gameObject); + }); + } + } + + // 设置项 + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Settings/Detect UI"), enableUI, () => EditorPrefs.SetBool(PREF_UI, !enableUI)); + menu.AddItem(new GUIContent("Settings/Detect 3D"), enable3D, () => EditorPrefs.SetBool(PREF_3D, !enable3D)); + menu.AddItem(new GUIContent("Settings/Reverse Order"), reverseOrder, () => EditorPrefs.SetBool(PREF_REVERSE, !reverseOrder)); + + menu.ShowAsContext(); + e.Use(); + } + } + } + + private static void AddEntry(List list, HashSet set, GameObject go, string source) + { + if (go == null || !set.Add(go)) return; + + string major = "3D"; + string minor = "Object"; + + if (source == "UI" || go.GetComponent() != null) + { + major = "UI"; minor = GetUIComponentType(go); + } + else if (go.GetComponent() != null) + { + major = "3D"; minor = "Collider"; + } + else if (go.GetComponent() != null) + { + major = "Mesh"; minor = go.GetComponent().GetType().Name; + } + + list.Add(new SelectionEntry + { + gameObject = go, + rootObject = GetRoot(go.transform), + depth = GetDepth(go.transform), + siblingIndex = go.transform.GetSiblingIndex(), + majorType = major, + minorType = minor + }); + } + + private static string GetUIComponentType(GameObject go) + { + if (go.GetComponent("TextMeshProUGUI")) return "TMPro"; + if (go.GetComponent()) return "Text"; + if (go.GetComponent