我是靠谱客的博主 笨笨柚子,最近开发中收集的这篇文章主要介绍ui控件不相应 unity3d_Unity UGUI —— 鼠标穿透UI问题(Unity官方的解决方法),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

简述

最近在用UGUI的时候遇到了鼠标穿透的问题,就是说在UGUI和3D场景混合的情况下,点击UI区域同时也会 触发3D中物体的鼠标事件。比如下图中

这里给Cube加了一个鼠标点击改变颜色的代码,如下

voidUpdate()

{if(Input.GetMouseButtonDown(0))

{

GetComponent().material.color = new Color(Random.value, Random.value, Random.value, 1.0f);

}

}

运行一下,会发现只要有鼠标点击(任何位置点击),Cube的颜色就会改变,根据代码我们知道这也是必然的,但是问题是如果Cube是一个3D世界中的mesh或者terrain,而button是UI的话也同样会出现同样的问题。

在游戏开发中我们的UI是始终出现在屏幕的,如果在一个战斗场景中用户点了UI战斗场景中的物体也会作出响应肯定是有问题的!

其实关于这个问题网上有不少解决方法了,但是总感觉没有一个是适合我的需求,或者说没有一个最好的答案。

其中提到最多的是利用EventSystem.current.IsPointerOverGameObject()来判断,这个方法的意义是判断鼠标是否点到了GameObject上面,这个GameObject包括UI也包括3D世界中的任何物体,所以他只能判断用户是都点到了东西。对于本文中的问题意义不是很大。那么这个问题到底该怎么解决呢?

原理

解决方法最终还是离不开射线检测,不过UGUI中已经封装了针对UI部分的射线碰撞的功能,那就是GraphicRaycaster类。里面有个Raycast方法如下,最终就是将射线碰撞到的点添加进resultAppendList数组。

public override void Raycast(PointerEventData eventData, ListresultAppendList)

{if (canvas == null)return;//Convert to view space

Vector2 pos;if (eventCamera == null)

pos= new Vector2(eventData.position.x / Screen.width, eventData.position.y /Screen.height);elsepos=eventCamera.ScreenToViewportPoint(eventData.position);//If it's outside the camera's viewport, do nothing

if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y >1f)return;float hitDistance = float.MaxValue;

Ray ray= newRay();if (eventCamera != null)

ray=eventCamera.ScreenPointToRay(eventData.position);if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects !=BlockingObjects.None)

{float dist = eventCamera.farClipPlane -eventCamera.nearClipPlane;if (blockingObjects == BlockingObjects.ThreeD || blockingObjects ==BlockingObjects.All)

{

RaycastHit hit;if (Physics.Raycast(ray, outhit, dist, m_BlockingMask))

{

hitDistance=hit.distance;

}

}if (blockingObjects == BlockingObjects.TwoD || blockingObjects ==BlockingObjects.All)

{

RaycastHit2D hit=Physics2D.Raycast(ray.origin, ray.direction, dist, m_BlockingMask);if (hit.collider != null)

{

hitDistance= hit.fraction *dist;

}

}

}

m_RaycastResults.Clear();

Raycast(canvas, eventCamera, eventData.position, m_RaycastResults);for (var index = 0; index < m_RaycastResults.Count; index++)

{var go =m_RaycastResults[index].gameObject;bool appendGraphic = true;if(ignoreReversedGraphics)

{if (eventCamera == null)

{//If we dont have a camera we know that we should always be facing forward

var dir = go.transform.rotation *Vector3.forward;

appendGraphic= Vector3.Dot(Vector3.forward, dir) > 0;

}else{//If we have a camera compare the direction against the cameras forward.

var cameraFoward = eventCamera.transform.rotation *Vector3.forward;var dir = go.transform.rotation *Vector3.forward;

appendGraphic= Vector3.Dot(cameraFoward, dir) > 0;

}

}if(appendGraphic)

{float distance = 0;if (eventCamera == null || canvas.renderMode ==RenderMode.ScreenSpaceOverlay)

distance= 0;else{// http://geomalgorithms.com/a06-_intersect-2.html

distance = (Vector3.Dot(go.transform.forward, go.transform.position - ray.origin) /Vector3.Dot(go.transform.forward, ray.direction));//Check to see if the go is behind the camera.

if (distance < 0)continue;

}if (distance >=hitDistance)continue;var castResult = newRaycastResult

{

gameObject=go,

module= this,

distance=distance,

index=resultAppendList.Count,

depth=m_RaycastResults[index].depth,

sortingLayer=canvas.sortingLayerID,

sortingOrder=canvas.sortingOrder

};

resultAppendList.Add(castResult);

}

}

}

从这个方法开始深入查看Unity UGUI源码你会发现,其实每个组件在创建的时候已经被添加进了一个公共列表,UGUI 源码中的GraphicRegistry类就是专门干这件事的。再看下Graphic类中的OnEnable方法

protected override voidOnEnable()

{base.OnEnable();

CacheCanvas();

GraphicRegistry.RegisterGraphicForCanvas(canvas,this);#if UNITY_EDITORGraphicRebuildTracker.TrackGraphic(this);#endif

if (s_WhiteTexture == null)

s_WhiteTexture=Texture2D.whiteTexture;

SetAllDirty();

SendGraphicEnabledDisabled();

}

看这句GraphicRegistry.RegisterGraphicForCanvas(canvas, this);就是注册需要做射线检测的UI组件。再看他内部是如何工作的

public static voidRegisterGraphicForCanvas(Canvas c, Graphic graphic)

{if (c == null)return;

IndexedSetgraphics;

instance.m_Graphics.TryGetValue(c,outgraphics);if (graphics != null)

{

graphics.Add(graphic);return;

}

graphics= new IndexedSet();

graphics.Add(graphic);

instance.m_Graphics.Add(c, graphics);

}

不过,问题又来了,为什么是添加进列表的对象都是Graphic类型呢?这跟ScrollRect,Button,Slider这些有关吗?其实,这就跟UGUI的类继承关系有关了,其实我们使用的UGUI中的每个组件都是继承自Graphic或者依赖一个继承自Graphic的组件

看一下UGUI的类层次结构就会一目了然,如下

看图就会更加清楚,在这我们可以把我们用到的UGUI的所有组件分为两类,1.是直接继承自Graphic的组件。2.是依赖于1的组件"[RequireComponent(typeof(Griphic))]",仔细想想会发现,所有组件都属于这两种中的某一种。

所以对所有Graphic进行Raycast其实就相当于对所有UI组件进行Raycast。

结合上面的知识所以,解决这个问题最好的方法是根据,UGUI的射线碰撞来做。这样会比较合理。

解决方案

这里我们直接在使用Input.GetMouseButtonDown(0)的地方加了一个检测函数,CheckGuiRaycastObjects,如下

boolCheckGuiRaycastObjects()

{

PointerEventData eventData= newPointerEventData(Main.Instance.eventSystem);

eventData.pressPosition=Input.mousePosition;

eventData.position=Input.mousePosition;

List list = new List();

Main.Instance.graphicRaycaster.Raycast(eventData, list);//Debug.Log(list.Count);

return list.Count > 0;

}

不过在使用时需要先获取两个加粗显示的变量,graphicRaycaster和eventSystem。

这两个变量分别对应的是Canvas中的GraphicRaycaster组件,和创建UI时自动生成的“EventSystem”中的EventSystem组件,用的是自己指定一下就可以。

然后在使用的时候可以这样:

voidUpdate ()

{if (CheckGuiRaycastObjects()) return;//Debug.Log(EventSystem.current.gameObject.name);

if (Input.GetMouseButtonDown(0))

{

Ray ray=Camera.main.ScreenPointToRay(Input.mousePosition);

RaycastHit hit;if (Physics.Raycast(ray, outhit))

{//do some thing

}

}

}

还有一个需要注意的地方就是,在做UI的时候一般会用一个Panel做跟目录,这个panel也会被添加到GraphicRegistry中的公共列表中,如果是这样的话记得把list.Count>0改成list.Count>1,或者直接删除Panel上的继承自Graphic的组件。

这样在结合着EventSystem.current.IsPointerOverGameObject()来使用就比较好了。

本文固定连接:http://www.cnblogs.com/fly-100/p/4570366.html

ok了,现在舒服多啦!

最后

以上就是笨笨柚子为你收集整理的ui控件不相应 unity3d_Unity UGUI —— 鼠标穿透UI问题(Unity官方的解决方法)的全部内容,希望文章能够帮你解决ui控件不相应 unity3d_Unity UGUI —— 鼠标穿透UI问题(Unity官方的解决方法)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部