打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【新提醒】【转载】Unity UGUI鼠标穿透UI问题
acondess 2015-8-9 13:57
树静风止: Input.GetMouseButtonDown(0)是只要鼠标左键按下了就为true,跟UGUI没半毛钱关系!
顶!偏题了   0分
回复金丝吊燕 2015-8-9 17:09
树静风止: Input.GetMouseButtonDown(0)是只要鼠标左键按下了就为true,跟UGUI没半毛钱关系!
对啊……如果是要点击物体,楼主可以用OnMouseDown(),UI的话有Button点击事件啊……

【转载】Unity UGUI鼠标穿透UI问题

热度 12638 2015-8-6 20:34 |个人分类:学习记录| 冲突

简述

 

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

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

    void Update()    {        if(Input.GetMouseButtonDown(0))        {            GetComponent<Renderer>().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, List<RaycastResult> resultAppendList)        {            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);            else                pos = 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 = new Ray();            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, out hit, 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 = new RaycastResult                    {                        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 void OnEnable()        {            base.OnEnable();            CacheCanvas();            GraphicRegistry.RegisterGraphicForCanvas(canvas, this);#if UNITY_EDITOR            GraphicRebuildTracker.TrackGraphic(this);#endif            if (s_WhiteTexture == null)                s_WhiteTexture = Texture2D.whiteTexture;            SetAllDirty();            SendGraphicEnabledDisabled();        }

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

        public static void RegisterGraphicForCanvas(Canvas c, Graphic graphic)        {            if (c == null)                return;            IndexedSet<Graphic> graphics;            instance.m_Graphics.TryGetValue(c, out graphics);            if (graphics != null)            {                graphics.Add(graphic);                return;            }            graphics = new IndexedSet<Graphic>();            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,如下

    bool CheckGuiRaycastObjects()    {        PointerEventData eventData = new PointerEventData(Main.Instance.eventSystem);        eventData.pressPosition = Input.mousePosition;        eventData.position = Input.mousePosition;        List<RaycastResult> list = new List<RaycastResult>();        Main.Instance.graphicRaycaster.Raycast(eventData, list);        //Debug.Log(list.Count);        return list.Count > 0;    }

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

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

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

    void Update ()     {        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, out hit))            {                //do some thing            }        }    }    

 

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

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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
UGUI -(unity3d 5)判断是否点击在UI 上 Bug,IsPointerOverGameObject()在移动输入模式检测失败
HTCVIVE开发日志
Unity3D uGUI源码阅读笔记(一)Button类
uGUI元素显示在角色的头顶上,ugui元素角色头顶
Unity UGUI之Canvas&EventSystem
只用一个脚本做一个 刮刮乐 案例,一不小心刮出来一个女朋友!
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服