untiy UGUI源码分析(2)射线检测

在EventSystem中提到使用射线检测对当前鼠标停放位置的UI进行检测,那么射线检测具体是怎么做的呢?我们继续看源码,在第一篇关于EventSystem的文章中讲过StandaloneInputModule 中通过GetMousePointerEventData方法确定当前选择的GameObject,而在GetMousePointerEventData通过射线检测确定选择的GameObject。

protected virtual MouseState GetMousePointerEventData(int id)
{
    // Populate the left button...
    PointerEventData leftData;
    var created = GetPointerData(kMouseLeftId, out leftData, true);

    leftData.Reset();

    if (created)
        leftData.position = input.mousePosition;

    Vector2 pos = input.mousePosition;
    if (Cursor.lockState == CursorLockMode.Locked)
    {
        // We don't want to do ANY cursor-based interaction when the mouse is locked
        leftData.position = new Vector2(-1.0f, -1.0f);
        leftData.delta = Vector2.zero;
    }
    else
    {
        leftData.delta = pos - leftData.position;
        leftData.position = pos;
    }
    leftData.scrollDelta = input.mouseScrollDelta;
    leftData.button = PointerEventData.InputButton.Left;
    eventSystem.RaycastAll(leftData, m_RaycastResultCache);
    var raycast = FindFirstRaycast(m_RaycastResultCache);
    leftData.pointerCurrentRaycast = raycast;
    m_RaycastResultCache.Clear();

    // copy the apropriate data into right and middle slots
    PointerEventData rightData;
    GetPointerData(kMouseRightId, out rightData, true);
    rightData.Reset();

    CopyFromTo(leftData, rightData);
    rightData.button = PointerEventData.InputButton.Right;

    PointerEventData middleData;
    GetPointerData(kMouseMiddleId, out middleData, true);
    middleData.Reset();

    CopyFromTo(leftData, middleData);
    middleData.button = PointerEventData.InputButton.Middle;

    m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
    m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
    m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);

    return m_MouseState;
}

eventSystem.RaycastAll(leftData, m_RaycastResultCache);
    var raycast = FindFirstRaycast(m_RaycastResultCache);
    leftData.pointerCurrentRaycast = raycast;

这三行代码就是射线检测的代码,RaycastAll首先遍历所有Raycaster,一般来说Raycaster包含多种类型比如PhysicsRaycaster和GraphicRaycaster。UI的检测版采用GraphicRaycaster。每一个Canvas都包含了一个GraphicRaycaster。所以RaycastAll方法会遍历所有的Canvas。

 

public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
{
    raycastResults.Clear();
    var modules = RaycasterManager.GetRaycasters();
    var modulesCount = modules.Count;
    for (int i = 0; i < modulesCount; ++i)
    {
        var module = modules[i];
        if (module == null || !module.IsActive())
            continue;

        module.Raycast(eventData, raycastResults);
    }

    raycastResults.Sort(s_RaycastComparer);//s_RaycastComparer是一个委托定义了一种排序顺序
}

RaycastAll会调用Raycaster的Raycast方法,接下来我们查看GraphicRaycaster的Raycast方法,代码有点长,输入两个参数第一个用于获取事件的信息,第二个参数输出射线检测结果。

Raycast的方法的主要步骤用注释写到代码中,我们主要就分析 Canvas的RenderMode为ScreenSpaceOverlay时的步骤

   public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
        {
            if (canvas == null)//判断当前Raycaster的canvas是否为空
                return;

            var canvasGraphics = GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);//判断这个canvas是否有支持射线检测的UI,没有的话return
            if (canvasGraphics == null || canvasGraphics.Count == 0)
                return;

            int displayIndex;
            var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference 当canvas的Rendermode为WorldSpace时会有一个eventCamera参数

            if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
                displayIndex = canvas.targetDisplay;
            else
                displayIndex = currentEventCamera.targetDisplay;

            var eventPosition = Display.RelativeMouseAt(eventData.position);//获取鼠标位置
            if (eventPosition != Vector3.zero)
            {
                // We support multiple display and display identification based on event position.

                int eventDisplayIndex = (int)eventPosition.z;

                // Discard events that are not part of this display so the user does not interact with multiple displays at once.
                if (eventDisplayIndex != displayIndex)
                    return;
            }
            else
            {
                // The multiple display system is not supported on all platforms, when it is not supported the returned position
                // will be all zeros so when the returned index is 0 we will default to the event data to be safe.
                eventPosition = eventData.position;

                // We dont really know in which display the event occured. We will process the event assuming it occured in our display.
            }

            // Convert to view space
            Vector2 pos;
            if (currentEventCamera == null)//这意味只rendermode为ScreenSpaceOverlay
            {
                // Multiple display support only when not the main display. For display 0 the reported
                // resolution is always the desktops resolution since its part of the display API,
                // so we use the standard none multiple display method. (case 741751)
                float w = Screen.width;
                float h = Screen.height;
                if (displayIndex > 0 && displayIndex < Display.displays.Length)
                {
                    w = Display.displays[displayIndex].systemWidth;
                    h = Display.displays[displayIndex].systemHeight;
                }
                pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
            }
            else
                pos = currentEventCamera.ScreenToViewportPoint(eventPosition);

            // If it's outside the camera's viewport, do nothing
            if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
                return;

            float hitDistance = float.MaxValue;//接下来就是判断射线是否发生碰撞

            Ray ray = new Ray();

            if (currentEventCamera != null)
                ray = currentEventCamera.ScreenPointToRay(eventPosition);//从屏幕空间发射一条射线
//接下来根据GraphicRaycaster中的blockObject以及blockMask参数分别讨论
            if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
            {
                float distanceToClipPlane = 100.0f;

                if (currentEventCamera != null)
                {
                    float projectionDirection = ray.direction.z;
                    distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
                        ? Mathf.Infinity
                        : Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection);
                }
#if PACKAGE_PHYSICS
                if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All)
                {
                    if (ReflectionMethodsCache.Singleton.raycast3D != null)
                    {
                        var hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, (int)m_BlockingMask);
                        if (hits.Length > 0)
                            hitDistance = hits[0].distance;
                    }
                }
#endif
#if PACKAGE_PHYSICS2D
                if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All)
                {
                    if (ReflectionMethodsCache.Singleton.raycast2D != null)
                    {
                        var hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, (int)m_BlockingMask);
                        if (hits.Length > 0)
                            hitDistance = hits[0].distance;
                    }
                }
#endif
            }

            m_RaycastResults.Clear();

            Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
//这个方法会从eventPosition发出一条射线找到所有与射线相交并且RaycastTarget的值为true的UI,并且根据深度排序保存在深度排序,后面的代码用于判断UI的正反,并输出RaycastResult,RaycastALL方法会根据RaycastResult进行排序最终确定我们选择的UI  
	int totalCount = m_RaycastResults.Count;
            for (var index = 0; index < totalCount; index++)
            {
                var go = m_RaycastResults[index].gameObject;
                bool appendGraphic = true;

                if (ignoreReversedGraphics)
                {
                    if (currentEventCamera == null)
                    {
                        // If we dont have a camera we know that we should always be facing forward
                        var dir = go.transform.rotation * Vector3.forward;
                        appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
                    }
                    else
                    {
                        // If we have a camera compare the direction against the cameras forward.
                        var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
                        appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
                    }
                }

                if (appendGraphic)
                {
                    float distance = 0;
                    Transform trans = go.transform;
                    Vector3 transForward = trans.forward;

                    if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
                        distance = 0;
                    else
                    {
                        // http://geomalgorithms.com/a06-_intersect-2.html
                        distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));

                        // Check to see if the go is behind the camera.
                        if (distance < 0)
                            continue;
                    }

                    if (distance >= hitDistance)
                        continue;

                    var castResult = new RaycastResult
                    {
                        gameObject = go,
                        module = this,
                        distance = distance,
                        screenPosition = eventPosition,
                        displayIndex = displayIndex,
                        index = resultAppendList.Count,
                        depth = m_RaycastResults[index].depth,
                        sortingLayer = canvas.sortingLayerID,
                        sortingOrder = canvas.sortingOrder,
                        worldPosition = ray.origin + ray.direction * distance,
                        worldNormal = -transForward
                    };
                    resultAppendList.Add(castResult);
                }
            }
        }

综上所述,射线检测的步骤如下(不考虑physicsraycaster): 

        1.首先遍历所有的Canvas,对每一个Canvas找到在eventPosition位置的UI

        2.然后利用一个排序规则对所有检测的到UI进行排序,最后选出第一个UI

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值