• 简述

    最近在用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官方的解决方法)的更多相关文章

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

    解决方案 : http://www.cnblogs.com/fly-100/p/4570366.html 这里我们直接在使用Input.GetMouseButtonDown(0)的地方加了一个检测函数 ...

  2. Unity中UGUI鼠标穿透UI问题的解决方法

    不过在使用时需要先获取两个红色显示的变量,graphicRaycaster和eventSystem. 这两个变量分别对应的是Canvas中的GraphicRaycaster组件和创建UI时自动生成的“ ...

  3. (转载)基于Unity~UGUI的简单UI框架(附UIFramework源码)

    此博客跟随siki老师的课程笔记生成,感谢siki老师的辛勤付出! 此框架功能较简单,适用于学习,可以很好的锻炼我们的设计思想 框架源码地址: UIFramework litjson.dll下载地址: ...

  4. unity, monoDevelop ide 代码提示不起作用的解决方法

    monoDevelop ide 代码提示不起作用,可能是因为ide里索引了一些不存在的文件,检查一下solution窗口里是否有文件变红,如下图中springControlEx.cs.将变红的文件re ...

  5. jQuery同步Ajax带来的UI线程阻塞问题及解决方法

    遇到了同步Ajax引起的UI线程阻塞问题,在此记录一下. 事情起因是这样的,因为页面上有多个相似的异步请求动作,本着提高代码可重用性的原则,我封装了一个名为getData的函数,它接收不同参数,只负责 ...

  6. MacBook鼠标指针乱窜/不受控制问题的解决方法

    用了快一年的MacBook Pro最近出现了奇怪的问题.出问题时,鼠标不受控制,屏幕上鼠标指针乱窜,还时不时自动点击,犹如电脑被人远程控制一般.不管是用trackpad还是用外接鼠标,都是同样问题.电 ...

  7. Windows 10,鼠标右键-发送到-桌面快捷方式缺失解决方法

    1-双击“我的电脑”. 进到这里 2-路径框修改为“shell:Sendto”,回车. 3-把“桌面快捷方式”黏贴到Sendto文件夹下

  8. WPF 平板上按钮点击不触发,鼠标点击触发的两种解决方法

    今天运行在windows平板上的程序,有个功能是弹出子窗体,点弹出窗体的关闭按钮,要点好几次才能触发.网上找了找,也有人与我类似的情形. 解决方法如下: public static void Disa ...

  9. Curved UI - VR Ready Solution To Bend Warp Your Canvas 1.7,1.8,2.2,2.3 四种版本压缩包(Unity UGUI曲面插件),可以兼容VRTK

    Curved UI - VR Ready Solution To Bend Warp Your Canvas 1.7,1.8,2.2,2.3 四种版本压缩包(Unity UGUI曲面插件) 可以兼容V ...

随机推荐

  1. Linux把查询结果写入到文本

    在Linux命令模式下,可以将查询结果写入文件.大概有两种方式,增量写入和覆盖写入. 增量写入: #iostat -m >> /tmp/iostat.txt 覆盖写入: #iostat - ...

  2. EasyRTMP实现Demux解析MP4文件进行rtmp推送实现RTMP直播功能

    本文转自EasyDarwin团队Kim的博客:http://blog.csdn.net/jinlong0603/article/details/52965101 前面已经介绍过EasyRTMP,这里不 ...

  3. EasyDarwin开源流媒体服务器支持basic基本认证和digest摘要自定义认证

    本文转自EasyDarwin开源团队成员的博客:http://blog.csdn.net/ss00_2012/article/details/52330838 在前面<EasyDarwin拉流支 ...

  4. react遇到的各种坑

    标签里用到<label for>的,for 要写成htmlFor 标签里的class要写成className 组件首字母一定要大写 单标签最后一定要闭合 如果html里要空格转义, 注意不 ...

  5. javascript JSON.parse和eval的区别

    SON.parse()用来将标准json字符串转换成js对象:eval()除了可以将json字符串(非标准的也可以,没有JSON.parse()要求严格)转换成js对象外还能用来动态执行js代码.例如 ...

  6. 在VC++空工程中使用MFC类,采用Unicode字符集后,运行工程程序报错的解决方案

    创建一个VC++空工程,将Project Properties->General->Use of MFC改为Use MFC in a Shared DLL 新建一个源文件,内容如下 #in ...

  7. 2017NOIP游记 (格式有点炸)

    NOIP游记 作者:一只小蒟蒻 时间可真快呀!还记得我第一次接触信息竞赛时,hello world都要调好久,不知不觉就考完了2017noip,自我感觉良好(虽然还是有很多不足). 这两个月的闭关,让 ...

  8. pexpect库学习之包装类详解

    在pexpect库中,包装类的构造参数使用的命令或者要包装命令的提示符,还可以通过这个包装类来修改命令的提示符,那么所谓的包装类实际就是用于给用户交互相应的子命令,它的实例方法主要是“run_comm ...

  9. MongoDB学习笔记(1):MongoDB的安装和说明

    MongoDB学习笔记(1):MongoDB的安装和说明 快速开始 下载地址 官网下载: https://www.mongodb.com/download-center?jmp=nav#communi ...

  10. Algorithm: Euler function

    欧拉函数. phi(n)表示比n小的与n互质的数的个数,比如 phi(1) = 1; phi(2) = 1; phi(3) = 2; phi(4) = 2; phi(5) = 4; 性质: 1. 如果 ...