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.结语。。
没有结语。。。。。。。。。。。。