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


    • 简述

      最近在用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了,现在舒服多啦!

  • 相关阅读:
    GO語言基礎教程:數組,切片,map
    GO語言視頻教程下載
    GO語言基礎教程:流程控制
    GO語言基礎教程:數據類型,變量,常量
    GO語言基礎教程:Hello world!
    GO語言基礎教程:序章
    騰訊RTX的API開發,給RTX開個天窗
    RTX的api開發實例
    [轉]redis;mongodb;memcache三者的性能比較
    [轉載]史上最强php生成pdf文件,html转pdf文件方法
  • 原文地址:https://www.cnblogs.com/nafio/p/9137481.html
Copyright © 2020-2023  润新知