您的位置:首页 > 移动开发 > Unity3D

Unity UGUI鼠标穿透UI问题(Unity官方的解决方法)

2015-09-20 12:35 573 查看
简述

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



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

view
sourceprint?

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数组。

view
sourceprint?

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.
{


081.
// http://geomalgorithms.com/a06-_intersect-2.html


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方法

view
sourceprint?

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组件。再看他内部是如何工作的

view
sourceprint?

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,如下

view
sourceprint?

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组件,用的是自己制定以下就可以。

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

view
sourceprint?

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了,现在舒服多啦!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: