原因

游戏画面由多个UI界面组成,当在场景进行拾取UI控件时,往往会先选中最外层的RectTransform控件对象,而不管这个对象有没有实质渲染,导致需要多次选择,才能选择到最贴近的UI控件。另外,由于Unity会优先选择SelectionBase属性的控件,导致拾取按钮文本会先拾取到按钮。

解决

通过在SceneView里面捕获鼠标点击,来自动拾取到可视UI控件,即Graphic控件,如下所示:

Unity Scene场景拾取可视UI控件_f5

限制

尽量在【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;
    }
}