(十)自定义GraphicRaycaster

1.前言

之前的文章中曾分析过unity事件系统中的rayCaster,其中GraphicRaycaster中曾分析过,射线在此类中的作用仅仅是为了检测与3D或者2D的碰撞距离,而ui检测时则使用如下代码:

RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera)

但是在VR中由于双目Camera问题,有时候会存在各种问题,比如射线检测时采用哪个Camera问题。所以比较容易解决的办法是直接采用射线检测。那么问题来了,如何解决ui的rect与射线的碰撞问题。unity为我们提供了PlaneRaycast方法。

2.Plane射线检测

Plane是结构体变量,首先在ui位置通过ui的位置和方向定义一个plane,然后定义一个射线ray。调用Plane的raycast放可以检测plane与ray是否相交,相交则返回相交位置与射线源点的距离center,然后通过center就可以获取到碰撞点。由于plane定义的是无限大的平面,所以检测碰撞点是否在ui的rect范围内,如果是则可以确定是在点击范围内。完整代码如下如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlaneCast : MonoBehaviour
{
    public RectTransform targetPlane;
    public GameObject cube;

    Plane plane;

    void CheckOnce()
    {
        Ray ray = new Ray(transform.position, transform.forward);

        float center;
        if(plane.Raycast(ray, out center))
        {
            Debug.Log("Ray casted with d " + center);
            Vector3 position =  ray.GetPoint(center);

            if(targetPlane.rect.Contains(position))
            {
                GameObject go = Instantiate(cube);
                go.transform.position = position;
                Debug.Log("Target contains");
            }
        }
    }

	// Use this for initialization
	void Start ()
    {
        plane = new Plane(targetPlane.forward, targetPlane.position);
        CheckOnce();
    }
	
	// Update is called once per frame
	void Update ()
    {
		if(Input.GetMouseButtonDown(0))
        {
            CheckOnce();
        }
	}
}

3.GraphicRaycaster

基于上述方法,对GraphicRaycaster做简单修改,即在原来通过判断点击点是否在UI范围内的方法改为射线检测。不过在计算时,如下代码中所有的坐标包括射线都转换到ui的局部坐标系内,这样plane就可以定义为Plane plane = new Plane(new Vector3(0, 0, -1), 0),而不用获取到ui的位置和方向。代码如下:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;

namespace VrInput
{
    [RequireComponent(typeof(Canvas))]
    [DisallowMultipleComponent]
    public class UIRaycaster : GraphicRaycaster
    {
        struct GraphicHit
        {
            public Graphic graph;
            public Vector3 worldPos;
        }
        private Canvas _canvas = null;
        private Canvas canvas
        {
            get { return _canvas != null ? _canvas : _canvas = GetComponent<Canvas>(); }
        }

        private Camera _eventCamera = null;
        public override Camera eventCamera
        {
            get { return _eventCamera != null ? _eventCamera : _eventCamera = Camera.main; }
        }

        public GameObject RayEmitter;

        public bool DrawDebugRay = false;
        public float Distance = 2000;

        public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
        {
            if (enabled == false || RayEmitter == null)
                return;
            Ray ray = new Ray(RayEmitter.transform.position, RayEmitter.transform.forward);

            float hitDistance = float.MaxValue;

            if (blockingObjects != BlockingObjects.None)
            {
                float dist = Distance;
                if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All)
                {
                    var hits = Physics.RaycastAll(ray, dist, m_BlockingMask);
                    if (hits.Length > 0 && hits[0].distance < hitDistance)
                    {
                        hitDistance = hits[0].distance;
                    }
                }

                if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All)
                {
                    var hits = Physics2D.GetRayIntersectionAll(ray, dist, m_BlockingMask);

                    if (hits.Length > 0 && hits[0].fraction * dist < hitDistance)
                    {
                        hitDistance = hits[0].fraction * dist;
                    }
                }
            }

            List<GraphicHit> sortedGraphics = new List<GraphicHit>();

            var list = GraphicRegistry.GetGraphicsForCanvas(canvas);
            for (int i = 0; i < list.Count; ++i)
            {
                GraphicHit hit;
                hit.graph = null;
                hit.worldPos = Vector3.zero;

                Graphic g = list[i];

                if (null == g || g.depth == -1 || !g.enabled || !g.raycastTarget || g.canvasRenderer.cull)
                {
                    continue;
                }

                if (!RayGraphicIntersectFlat(ray, g, hitDistance, ref hit))
                {
                    continue;
                }

                sortedGraphics.Add(hit);
            }

            sortedGraphics.Sort((g1, g2) => g2.graph.depth.CompareTo(g1.graph.depth));

            if (sortedGraphics.Count == 0)
            {
                return;
            }
            if (DrawDebugRay)
                Debug.DrawLine(ray.origin, sortedGraphics[0].worldPos, Color.green);

            for (int i = 0; i < sortedGraphics.Count; ++i)
            {
                var castResult = new RaycastResult
                {
                    gameObject = sortedGraphics[i].graph.gameObject,
                    module = this,
                    distance = (sortedGraphics[i].worldPos - ray.origin).magnitude,
                    index = resultAppendList.Count,
                    depth = sortedGraphics[i].graph.depth,
                    worldPosition = sortedGraphics[i].worldPos,
                    sortingLayer = canvas.sortingLayerID,
                    sortingOrder = canvas.sortingOrder,
                };
                resultAppendList.Add(castResult);
            }
        }
        private bool RayGraphicIntersectFlat(Ray ray, Graphic graphic, float dist, ref GraphicHit hit)
        {
            hit.graph = null;

            Ray localRay = ray;

            Matrix4x4 worldToLocal = graphic.transform.worldToLocalMatrix;

            localRay.origin = worldToLocal.MultiplyPoint(ray.origin);
            localRay.direction = worldToLocal.MultiplyVector(ray.direction);

            localRay.direction.Normalize();

            Rect rc = graphic.rectTransform.rect;

            float t = -1;

            if (!RayRectIntersect(localRay, rc, dist, out t))
            {
                return false;
            }

            Matrix4x4 localToWorld = worldToLocal.inverse;

            hit.graph = graphic;
            hit.worldPos = localToWorld.MultiplyPoint(localRay.GetPoint(t));

            //Use Graphic.Raycast to detected whether the hit position has been discard by Mask2D or Mask
            return graphic.Raycast(eventCamera.WorldToScreenPoint(hit.worldPos), eventCamera);
        }

        public bool RayRectIntersect(Ray ray, Rect rc, float dist, out float t)
        {

            Plane plane = new Plane(new Vector3(0, 0, -1), 0);

            if (!plane.Raycast(ray, out t))
            {
                return false;
            }

            if (t < 0 || t > dist)
            {
                return false;
            }

            return rc.Contains(ray.GetPoint(t));
        }
    }
}

4.结语。。

没有结语。。。。。。。。。。。。

posted @ 2020-04-11 19:53  81192  阅读(745)  评论(0编辑  收藏  举报