在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变量用来指定这两个组件即可。
使用方法
当检测到有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;
}
}
}