Unity中解决UGUI射线穿透问题| 8月更文挑战

2,763 阅读2分钟

在2D和3D场景混合下,使用UGUI经常会出现射线检测穿透的问题。在3D场景中我们通过射线检测来获取鼠标在场景中点击坐标,通常代码如下:

if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitinfo;
            bool isCollider =  Physics.Raycast(ray, out hitinfo);
            if(isCollider&&hitinfo.collider.tag == Tags.Ground)
            {
 				//do some thing
                dir = hitinfo.point;
            }
        }

但像RPG这类型的游戏制作时,通常会在Camera组件下放置Canvas用来进行UGUI的渲染,这样的UI界面会有多个可供交互的Button按钮。❌问题就出现了,比如某个按钮的功能是打开背包,射线检测用来在场景中根据点击处生成一个小树,当我们点击按钮的时候触发了打开背包的方法,同时射线在场景中的效果也出现了。✔正常的情况下,点击背包按钮,就不需要生成一颗树了。

现在需要解决这样的问题。
官方在UGUI中已经 封装了针对射线检测的很多功能。GraphicRaycaster类里面有个Raycast方法,会将射线碰撞到的东西添加到List resultAppendList中,RaycastResult对象会包含检测对象的很多属性信息,这里暂时不用使用到,只需要检测到有与UIGUI的组件接触,我们就不去执行自定义射线与场景的交互就好。
这是反编译DLL的源码:

public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
        {
            if (canvas == null)
            {
                return;
            }

            IList<Graphic> graphicsForCanvas = GraphicRegistry.GetGraphicsForCanvas(canvas);
            if (graphicsForCanvas == null || graphicsForCanvas.Count == 0)
            {
                return;
            }

            Camera eventCamera = this.eventCamera;
            int num = (canvas.renderMode != 0 && !(eventCamera == null)) ? eventCamera.targetDisplay : canvas.targetDisplay;
            Vector3 vector = Display.RelativeMouseAt(eventData.position);
            if (vector != Vector3.zero)
            {
                int num2 = (int)vector.z;
                if (num2 != num)
                {
                    return;
                }
            }
            else
            {
                vector = eventData.position;
            }

            Vector2 vector2;
            if (eventCamera == null)
            {
                float num3 = Screen.width;
                float num4 = Screen.height;
                if (num > 0 && num < Display.displays.Length)
                {
                    num3 = Display.displays[num].systemWidth;
                    num4 = Display.displays[num].systemHeight;
                }

                vector2 = new Vector2(vector.x / num3, vector.y / num4);
            }
            else
            {
                vector2 = eventCamera.ScreenToViewportPoint(vector);
            }

            if (vector2.x < 0f || vector2.x > 1f || vector2.y < 0f || vector2.y > 1f)
            {
                return;
            }

            float num5 = float.MaxValue;
            Ray r = default(Ray);
            if (eventCamera != null)
            {
                r = eventCamera.ScreenPointToRay(vector);
            }

            if (canvas.renderMode != 0 && blockingObjects != 0)
            {
                float f = 100f;
                if (eventCamera != null)
                {
                    float z = r.direction.z;
                    f = (Mathf.Approximately(0f, z) ? float.PositiveInfinity : Mathf.Abs((eventCamera.farClipPlane - eventCamera.nearClipPlane) / z));
                }

                if ((blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All) && ReflectionMethodsCache.Singleton.raycast3D != null)
                {
                    RaycastHit[] array = ReflectionMethodsCache.Singleton.raycast3DAll(r, f, m_BlockingMask);
                    if (array.Length != 0)
                    {
                        num5 = array[0].distance;
                    }
                }

                if ((blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All) && ReflectionMethodsCache.Singleton.raycast2D != null)
                {
                    RaycastHit2D[] array2 = ReflectionMethodsCache.Singleton.getRayIntersectionAll(r, f, m_BlockingMask);
                    if (array2.Length != 0)
                    {
                        num5 = array2[0].distance;
                    }
                }
            }

            m_RaycastResults.Clear();
            Raycast(canvas, eventCamera, vector, graphicsForCanvas, m_RaycastResults);
            int count = m_RaycastResults.Count;
            for (int i = 0; i < count; i++)
            {
                GameObject gameObject = m_RaycastResults[i].gameObject;
                bool flag = true;
                if (ignoreReversedGraphics)
                {
                    if (eventCamera == null)
                    {
                        Vector3 rhs = gameObject.transform.rotation * Vector3.forward;
                        flag = (Vector3.Dot(Vector3.forward, rhs) > 0f);
                    }
                    else
                    {
                        Vector3 b = eventCamera.transform.rotation * Vector3.forward * eventCamera.nearClipPlane;
                        flag = (Vector3.Dot(gameObject.transform.position - eventCamera.transform.position - b, gameObject.transform.forward) >= 0f);
                    }
                }

                if (!flag)
                {
                    continue;
                }

                float num6 = 0f;
                Transform transform = gameObject.transform;
                Vector3 forward = transform.forward;
                if (eventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
                {
                    num6 = 0f;
                }
                else
                {
                    num6 = Vector3.Dot(forward, transform.position - r.origin) / Vector3.Dot(forward, r.direction);
                    if (num6 < 0f)
                    {
                        continue;
                    }
                }

                if (!(num6 >= num5))
                {
                    RaycastResult raycastResult = default(RaycastResult);
                    raycastResult.gameObject = gameObject;
                    raycastResult.module = this;
                    raycastResult.distance = num6;
                    raycastResult.screenPosition = vector;
                    raycastResult.displayIndex = num;
                    raycastResult.index = resultAppendList.Count;
                    raycastResult.depth = m_RaycastResults[i].depth;
                    raycastResult.sortingLayer = canvas.sortingLayerID;
                    raycastResult.sortingOrder = canvas.sortingOrder;
                    raycastResult.worldPosition = r.origin + r.direction * num6;
                    raycastResult.worldNormal = -forward;
                    RaycastResult item = raycastResult;
                    resultAppendList.Add(item);
                }
            }
        }

具体实现方法如下,学习了www.cnblogs.com/fly-100/p/4…这篇博客文章

bool CheckGuiRaycastObjects()
    {
        PointerEventData eventData = new PointerEventData(eventSystem);
        eventData.pressPosition = Input.mousePosition;
        eventData.position = Input.mousePosition;

        List<RaycastResult> list = new List<RaycastResult>();
        graphicRaycaster.Raycast(eventData, list);
        return list.Count > 0;
    }

这两个变量分别对应的是Canvas中的GraphicRaycaster组件,和创建UI时自动生成的“EventSystem”中的EventSystem组件。

使用两个public变量用来指定这两个组件即可。
image.pngimage.pngimage.png
使用方法
当检测到有UGUI的点击时,将不会再进行自定义射线的检测,从而避免问题的发生。

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (CheckGuiRaycastObjects())
            {
                return;
            }
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitinfo;
            bool isCollider =  Physics.Raycast(ray, out hitinfo);
            if(isCollider&&hitinfo.collider.tag == Tags.Ground)
            {
                dir = hitinfo.point;
            }
        }
    }