原因
游戏画面由多个UI界面组成,当在场景进行拾取UI控件时,往往会先选中最外层的RectTransform控件对象,而不管这个对象有没有实质渲染,导致需要多次选择,才能选择到最贴近的UI控件。另外,由于Unity会优先选择SelectionBase属性的控件,导致拾取按钮文本会先拾取到按钮。
解决
通过在SceneView里面捕获鼠标点击,来自动拾取到可视UI控件,即Graphic控件,如下所示:
限制
尽量在【Move Tool】下使用这个拾取工具,而不是在【Rect Tool】下,否则可能会采集不到正确的控件。
源码
https://gist.github.com/akof1314/bef2944c50277aaeb3dd99a8f56d83e6
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
public class SceneViewPickUI
{
[InitializeOnLoadMethod]
private static void Initialize()
{
SceneView.onSceneGUIDelegate += OnSceneGUIDelegate;
}
private static void OnSceneGUIDelegate(SceneView sceneView)
{
if (!sceneView.in2DMode)
{
return;
}
if (pick == null)
{
pick = new SceneViewPickUI();
}
pick.OnGUI();
}
private const string menuName = "Tools/Pick UI";
[MenuItem(menuName, false, 0)]
private static void ShowMenuPick()
{
if (pick == null)
{
pick = new SceneViewPickUI();
}
pick.SetUse();
}
private static SceneViewPickUI pick;
private bool m_UsePick;
private bool m_RectSelecting;
private Vector2 m_SelectStartPoint;
enum SelectionType { Normal, Additive, Invert }
private SceneViewPickUI()
{
m_UsePick = EditorPrefs.GetBool("SceneViewPickUI", false);
Menu.SetChecked(menuName, m_UsePick);
}
private void SetUse()
{
m_UsePick = !m_UsePick;
EditorPrefs.SetBool("SceneViewPickUI", m_UsePick);
Menu.SetChecked(menuName, m_UsePick);
}
private void OnGUI()
{
if (!m_UsePick)
{
return;
}
Handles.BeginGUI();
var usePick = GUILayout.Toggle(m_UsePick, "Pick UI");
if (usePick != m_UsePick)
{
SetUse();
}
Handles.EndGUI();
if (!m_UsePick)
{
return;
}
Event evt = Event.current;
Vector2 mousePos = evt.mousePosition;
int id = GUIUtility.GetControlID(FocusType.Passive);
switch (evt.GetTypeForControl(id))
{
case EventType.Layout:
HandleUtility.AddDefaultControl(id);
break;
case EventType.MouseDown:
if (HandleUtility.nearestControl == id && evt.button == 0)
{
GUIUtility.hotControl = id;
m_SelectStartPoint = mousePos;
m_RectSelecting = false;
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == id)
{
if (!m_RectSelecting && (mousePos - m_SelectStartPoint).magnitude > 6f)
{
m_RectSelecting = true;
}
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == id && evt.button == 0)
{
GUIUtility.hotControl = 0;
if (m_RectSelecting)
{
m_RectSelecting = false;
}
else
{
SelectionType selectionType = SelectionType.Normal;
if (evt.shift)
{
selectionType = SelectionType.Additive;
}
else if (EditorGUI.actionKey)
{
selectionType = SelectionType.Invert;
}
if (UpdateSelection(selectionType))
{
evt.Use();
}
}
}
break;
}
}
private bool UpdateSelection(SelectionType type)
{
var overlapped = new List<GameObject>();
do
{
var go = HandleUtility.PickGameObject(Event.current.mousePosition, false, overlapped.ToArray());
if (!go)
{
break;
}
var rt = go.GetComponent<RectTransform>();
if (!rt)
{
break;
}
var mg = go.GetComponent<Graphic>();
if (mg && mg.color.a > 0 // 本身颜色不透明
&& mg.canvasRenderer.materialCount > 0 // 没有禁用组件
&& mg.canvasRenderer.GetInheritedAlpha() > 0 // CanvasGroups 导致的 Alpha 不为0
//&& !go.GetComponent<Empty4Raycast>() // 空网格不选择
)
{
//Debug.LogWarning(go, go);
switch (type)
{
case SelectionType.Additive:
{
List<Object> newSelection = new List<Object>();
newSelection.AddRange(Selection.objects);
newSelection.Add(go);
Selection.objects = newSelection.ToArray();
}
break;
case SelectionType.Invert:
{
List<Object> newSelection = new List<Object>();
newSelection.AddRange(Selection.objects);
if (newSelection.Contains(go))
{
newSelection.Remove(go);
}
else
{
newSelection.Add(go);
}
Selection.objects = newSelection.ToArray();
}
break;
case SelectionType.Normal:
Selection.activeObject = go;
break;
}
return true;
}
if (overlapped.Count > 0 && overlapped[overlapped.Count - 1] == go)
{
break;
}
overlapped.Add(go);
} while (true);
return false;
}
}