(十)自定义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>();