180 lines
6.7 KiB
Plaintext
180 lines
6.7 KiB
Plaintext
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<GameObject> processedObjects = new HashSet<GameObject>();
|
||
List<SelectionEntry> entries = new List<SelectionEntry>();
|
||
|
||
// 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<RectTransform>(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<Renderer>(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<SelectionEntry> list, HashSet<GameObject> set, GameObject go, string source)
|
||
{
|
||
if (go == null || !set.Add(go)) return;
|
||
|
||
string major = "3D";
|
||
string minor = "Object";
|
||
|
||
if (source == "UI" || go.GetComponent<RectTransform>() != null)
|
||
{
|
||
major = "UI"; minor = GetUIComponentType(go);
|
||
}
|
||
else if (go.GetComponent<Collider>() != null)
|
||
{
|
||
major = "3D"; minor = "Collider";
|
||
}
|
||
else if (go.GetComponent<Renderer>() != null)
|
||
{
|
||
major = "Mesh"; minor = go.GetComponent<Renderer>().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<Text>()) return "Text";
|
||
if (go.GetComponent<Button>()) return "Button";
|
||
if (go.GetComponent<Image>()) return "Image";
|
||
return "Rect";
|
||
}
|
||
|
||
private static GameObject GetRoot(Transform t)
|
||
{
|
||
Transform curr = t; while (curr.parent != null) curr = curr.parent; return curr.gameObject;
|
||
}
|
||
|
||
private static int GetDepth(Transform t)
|
||
{
|
||
int d = 0; while (t.parent != null) { d++; t = t.parent; }
|
||
return d;
|
||
}
|
||
|
||
private class SelectionEntry
|
||
{
|
||
public GameObject gameObject;
|
||
public GameObject rootObject;
|
||
public int depth;
|
||
public int siblingIndex;
|
||
public string majorType;
|
||
public string minorType;
|
||
}
|
||
} |