-
简述
最近在用UGUI的时候遇到了鼠标穿透的问题,就是说在UGUI和3D场景混合的情况下,点击UI区域同时也会 触发3D中物体的鼠标事件。比如下图中
这里给Cube加了一个鼠标点击改变颜色的代码,如下
1.
void
Update()
2.
{
3.
if
(Input.GetMouseButtonDown(
0
))
4.
{
5.
GetComponent<Renderer>().material.color =
new
Color(Random.value, Random.value, Random.value,
1
.0f);
6.
}
7.
}
运行一下,会发现只要有鼠标点击(任何位置点击),Cube的颜色就会改变,根据代码我们知道这也是必然的,但是问题是如果Cube是一个3D世界中的mesh或者terrain,而button是UI的话也同样会出现同样的问题。
在游戏开发中我们的UI是始终出现在屏幕的,如果在一个战斗场景中用户点了UI战斗场景中的物体也会作出响应肯定是有问题的!
其实关于这个问题网上有不少解决方法了,但是总感觉没有一个是适合我的需求,或者说没有一个最好的答案。
其中提到最多的是利用EventSystem.current.IsPointerOverGameObject()来判断,这个方法的意义是判断鼠标是否点到了GameObject上面,这个GameObject包括UI也包括3D世界中的任何物体,所以他只能判断用户是都点到了东西。对于本文中的问题意义不是很大。那么这个问题到底该怎么解决呢?
原理
解决方法最终还是离不开射线检测,不过UGUI中已经封装了针对UI部分的射线碰撞的功能,那就是GraphicRaycaster类。里面有个Raycast方法如下,最终就是将射线碰撞到的点添加进resultAppendList数组。
001.
public
override
void
Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
002.
{
003.
if
(canvas ==
null
)
004.
return
;
005.
006.
// Convert to view space
007.
Vector2 pos;
008.
if
(eventCamera ==
null
)
009.
pos =
new
Vector2(eventData.position.x / Screen.width, eventData.position.y / Screen.height);
010.
else
011.
pos = eventCamera.ScreenToViewportPoint(eventData.position);
012.
013.
// If it's outside the camera's viewport, do nothing
014.
if
(pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
015.
return
;
016.
017.
float
hitDistance =
float
.MaxValue;
018.
019.
Ray ray =
new
Ray();
020.
021.
if
(eventCamera !=
null
)
022.
ray = eventCamera.ScreenPointToRay(eventData.position);
023.
024.
if
(canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
025.
{
026.
float
dist = eventCamera.farClipPlane - eventCamera.nearClipPlane;
027.
028.
if
(blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All)
029.
{
030.
RaycastHit hit;
031.
if
(Physics.Raycast(ray, out hit, dist, m_BlockingMask))
032.
{
033.
hitDistance = hit.distance;
034.
}
035.
}
036.
037.
if
(blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All)
038.
{
039.
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, dist, m_BlockingMask);
040.
041.
if
(hit.collider !=
null
)
042.
{
043.
hitDistance = hit.fraction * dist;
044.
}
045.
}
046.
}
047.
048.
m_RaycastResults.Clear();
049.
Raycast(canvas, eventCamera, eventData.position, m_RaycastResults);
050.
051.
for
(var index =
0
; index < m_RaycastResults.Count; index++)
052.
{
053.
var go = m_RaycastResults[index].gameObject;
054.
bool appendGraphic =
true
;
055.
056.
if
(ignoreReversedGraphics)
057.
{
058.
if
(eventCamera ==
null
)
059.
{
060.
// If we dont have a camera we know that we should always be facing forward
061.
var dir = go.transform.rotation * Vector3.forward;
062.
appendGraphic = Vector3.Dot(Vector3.forward, dir) >
0
;
063.
}
064.
else
065.
{
066.
// If we have a camera compare the direction against the cameras forward.
067.
var cameraFoward = eventCamera.transform.rotation * Vector3.forward;
068.
var dir = go.transform.rotation * Vector3.forward;
069.
appendGraphic = Vector3.Dot(cameraFoward, dir) >
0
;
070.
}
071.
}
072.
073.
if
(appendGraphic)
074.
{
075.
float
distance =
0
;
076.
077.
if
(eventCamera ==
null
|| canvas.renderMode == RenderMode.ScreenSpaceOverlay)
078.
distance =
0
;
079.
else
080.
{
082.
distance = (Vector3.Dot(go.transform.forward, go.transform.position - ray.origin) / Vector3.Dot(go.transform.forward, ray.direction));
083.
084.
// Check to see if the go is behind the camera.
085.
if
(distance <
0
)
086.
continue
;
087.
}
088.
089.
if
(distance >= hitDistance)
090.
continue
;
091.
092.
var castResult =
new
RaycastResult
093.
{
094.
gameObject = go,
095.
module =
this
,
096.
distance = distance,
097.
index = resultAppendList.Count,
098.
depth = m_RaycastResults[index].depth,
099.
sortingLayer = canvas.sortingLayerID,
100.
sortingOrder = canvas.sortingOrder
101.
};
102.
resultAppendList.Add(castResult);
103.
}
104.
}
105.
}
从这个方法开始深入查看Unity UGUI源码你会发现,其实每个组件在创建的时候已经被添加进了一个公共列表,UGUI 源码中的GraphicRegistry类就是专门干这件事的。再看下Graphic类中的OnEnable方法
01.
protected
override
void
OnEnable()
02.
{
03.
base.OnEnable();
04.
CacheCanvas();
05.
GraphicRegistry.RegisterGraphicForCanvas(canvas,
this
);
06.
07.
#
if
UNITY_EDITOR
08.
GraphicRebuildTracker.TrackGraphic(
this
);
09.
#endif
10.
if
(s_WhiteTexture ==
null
)
11.
s_WhiteTexture = Texture2D.whiteTexture;
12.
13.
SetAllDirty();
14.
15.
SendGraphicEnabledDisabled();
16.
}
看这句GraphicRegistry.RegisterGraphicForCanvas(canvas, this);就是注册需要做射线检测的UI组件。再看他内部是如何工作的
01.
public
static
void
RegisterGraphicForCanvas(Canvas c, Graphic graphic)
02.
{
03.
if
(c ==
null
)
04.
return
;
05.
06.
IndexedSet<Graphic> graphics;
07.
instance.m_Graphics.TryGetValue(c, out graphics);
08.
09.
if
(graphics !=
null
)
10.
{
11.
graphics.Add(graphic);
12.
return
;
13.
}
14.
15.
graphics =
new
IndexedSet<Graphic>();
16.
graphics.Add(graphic);
17.
instance.m_Graphics.Add(c, graphics);
18.
}
不过,问题又来了,为什么是添加进列表的对象都是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,如下
01.
bool CheckGuiRaycastObjects()
02.
{
03.
PointerEventData eventData =
new
PointerEventData(Main.Instance.<strong>eventSystem</strong>);
04.
eventData.pressPosition = Input.mousePosition;
05.
eventData.position = Input.mousePosition;
06.
07.
List<RaycastResult> list =
new
List<RaycastResult>();
08.
Main.Instance.<strong>graphicRaycaster</strong>.Raycast(eventData, list);
09.
//Debug.Log(list.Count);
10.
return
list.Count >
0
;
11.
}
不过在使用时需要先获取两个加粗显示的变量,graphicRaycaster和eventSystem。
这两个变量分别对应的是Canvas中的GraphicRaycaster组件,和创建UI时自动生成的“EventSystem”中的EventSystem组件,用的是自己制定以下就可以。
然后在使用的时候可以这样:
01.
void
Update ()
02.
{
03.
if
(CheckGuiRaycastObjects())
return
;
04.
//Debug.Log(EventSystem.current.gameObject.name);
05.
if
(Input.GetMouseButtonDown(
0
))
06.
{
07.
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
08.
RaycastHit hit;
09.
10.
if
(Physics.Raycast(ray, out hit))
11.
{
12.
//do some thing
13.
}
14.
}
15.
}
还有一个需要注意的地方就是,在做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了,现在舒服多啦!
Unity UGUI鼠标穿透UI问题(Unity官方的解决方法)
最新推荐文章于 2024-08-16 16:32:42 发布