首页 > 其他分享 >Unity怎么判断图形(Graphic)是否被遮挡

Unity怎么判断图形(Graphic)是否被遮挡

时间:2024-04-10 21:37:34浏览次数:28  
标签:canvas Graphic 遮挡 Unity graphic components var alpha

起因:

最近领了个需求,需要给项目的弱引导增加个功能,判断它是否被其他UI遮挡住,如果被遮挡了就需要实时将它隐藏,遮挡结束则恢复显示,这个需求乍一看似乎有点不太变态,但细细想想似乎还是能够做到,以下将我的经验分享一下。

遮挡判断:

要想解决这个问题,最重要的是要知道一个ui怎么算是被遮挡了。

  • 第一个思路是,我认为一张图像的组成是由贴图采样得到的像素点中所有RGBA值中所有alpha值大于0的部分, 我们需要用最后输出到屏幕上的贴图和原贴图做逐个像素的对比,如果有不一致的地方则认为被遮挡了。但这个明显实现起来过于耗,而且结果不一定尽人意,假如仅有一个像素点被覆盖了,我们也认为它被遮挡了是比较离谱的,虽然我们可以定义个百分比的阙值,但毕竟还是对需求妥协了,所以不如换个更简单的思路。

  • 第二个思路,判断网格中所有顶点的采样得到像素值是否被其他颜色覆盖了,这样确实简化了不少,但是要想判断像素值,势必要开启所有图集资源的读写,仅仅是为了这个需求显然是不合理。

  • 第三个思路,所以我们简化一步到位,直接判断ui的中心点,并且不判断它的像素值,而是通过一条射线找到该点上的所有graph组件,剔除掉透明的组件并排序好层级关系后,判断该ui是否被遮挡。

在遮挡这,我们即可采取第三个思路来做处理,还是额外说明一点,如果graph是目标子节点,也可以认为它并没遮挡目标,因为它本身就是目标节点的一部分。

自身显隐

除了遮挡会影响弱引导的指引,指向目标本身的显隐也会影响到弱引导的显示,所以我大致总结下有哪些因素会导致指向目标我们“看不见”

  1. 目标的gameObject.activeInHierarchy为false

  2. 目标graph的alpha值为0

  3. 目标上有CanvasGroup组件或其父对象上有CanvasGroup组件,且CanvasGroup组件的alpha值为0

  4. 目标被Mask组件或者RectMask2D组件裁剪

  5. 目标的canvasRender组件的cull属性为false(cull:指示是否忽略该渲染器发射的几何形状)

  6. 目标的canvasRender组件的absoluteDepth属性小于0(absoluteDepth:渲染器相对于根画布的深度)

  7. 目标不在屏幕范围内

思路总结

综上所述,我们大致就能得出一个结论,当没有任何非透明ui遮挡目标,且目标也未被隐藏,我们就认为弱引导可以正常的指向它,反之则需要隐藏弱引导。
接下来,我将通过代码实现下我们上面所诉的每个条件。

获得UI中心点上所有的UI

        var rectTransform = targetGraphic.transform as RectTransform;
        var worldPos = rectTransform.TransformPoint(rectTransform.rect.center);
        var screenPos = Camera.main.WorldToScreenPoint(worldPos);   // 用自己项目的ui摄像机来做转换,我这里demo演示用的Camera.main

        // 简化处理,拿到场景中所有的Canvas
        List<Canvas> canvases = GameObject.FindObjectsOfType<Canvas>().ToList();
        for (int i = 0, icnt = canvases.Count; i < icnt; i++)
        {
            // 获得该canvas画布下所有注册进去的Graphic
            var canvas = canvases[i];
            var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
        }

判断ui是否被Mask遮挡住

先总结下Mask的实现原理,Mask激活时,会修改MaskGraphic的材质,该材质会对每个像素点进行标记,将标记结果存入模板缓存中,当子级UI渲染中,如果未通过模板缓冲区的测试,则会丢弃到未通过的像素。
一言难尽,先放代码,后面另开篇文章总结怎么判断ui是否被Mask和RectMask2D遮罩,它的原理和它能否被点击到相关

    /// <summary>
    /// 是否有被Mask组件或者RectMask2D裁剪,可以假设Rect中心点不显示了,就认为被裁剪了(当然也可以4个边点,我这里从简计算)
    /// </summary>
    /// <param name="graphic"></param>
    /// <returns></returns>
    private bool IsMaskCull(Image graphic, Camera eventCamera)
    {
        if (graphic == null)
        {
            return false;
        }

        var t = graphic.transform;
        var worldPos = t.TransformPoint((t as RectTransform).rect.center);
        var screenPos = eventCamera.WorldToScreenPoint(worldPos);
        
        // 如果有Canvas,且Canvas的overrideSorting属性为true,则上层不再影响它们
        bool continueTraversal = true;
        bool valid = true;
        List<Component> components = new List<Component>();
        while (t != null)
        {
            t.GetComponents(components);
            for (int i = 0; i < components.Count; i++)
            {
                var canvas = components[i] as Canvas;
                if (canvas != null && canvas.overrideSorting)
                {
                    continueTraversal = true;
                }

                var mask = components[i] as Mask;
                var rectMask2D = components[i] as RectMask2D;
                if (mask == null && rectMask2D == null)
                {
                    continue;
                }
                var filter = components[i] as ICanvasRaycastFilter; // 很有意思一点,对于Mask和RectMask2D 点不到跟看不到一个道理
                valid = filter.IsRaycastLocationValid(screenPos, eventCamera);

                if (!valid)
                {
                    return true;
                }
            }
            t = continueTraversal ? t.parent : null;
        }
        
        return false;
    }

判断canvasGroup是否alpha为0

    /// <summary>
    /// 返回节点或其父节点最近邻的CanvasGroup的alpah是否为0
    /// </summary>
    /// <returns></returns>
    private bool IsCanvasGroupAlphaZero(Graphic graphic)
    {
        List<Component> components = new List<Component>();
        var t = graphic.transform;
        bool hasCanvasGroup = false;
        float alpha = 1f;

        while (t != null)
        {
            if (hasCanvasGroup)
            {
                break;
            }
            
            t.GetComponents(components);
            for (int i = 0; i < components.Count; i++)
            {
                // 拿到最近邻的alpha
                var group = components[i] as CanvasGroup;
                if (group == null || !group.enabled)
                {
                    continue;
                }

                alpha = group.alpha;
                hasCanvasGroup = true;
                break;
            }
            t = t.parent;
        }

        return alpha == 0;
    }

判断grapha是否包含目标中心点

            // 如果aabb包围盒不包含点击点则剔除
            if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, eventPosition, Camera.main, graphic.raycastPadding))
            {
                continue;
            }

判断是否忽略canvasRender渲染

            // 如果忽略canvasRender的渲染,或graphic深度不正常(graphic.depth == graphic.canvasRenderer.absoluteDepth)则剔除
            if (graphic.canvasRenderer.cull || graphic.depth == -1)
            {
                continue;
            }

判断材质是否透明

            // 如果材质透明则剔除
            if (graphic.color.a == 0f)
            {
                continue;
            }

判断Canvs上的点击点是否在屏幕范围内

考虑多显示器的情况,获得鼠标在某个显示器上真正的坐标,判断鼠标有没有超出该显示器的长宽范围

    /// <summary>
    /// 判断Graphic是否在屏幕范围内,仅讨论canvas renderMode是Camera的情况
    /// </summary>
    /// <param name="graphic"></param>
    /// <param name="eventCamera"></param>
    /// <returns></returns>
    private bool InScreen(Canvas canvas, PointerEventData eventData, Camera eventCamera)
    {
        // 该情况过于复杂,大概说下流程
        // 1. 首先调用Display.RelativeMouseAt(eventData.position)方法来获取事件相对于屏幕的返回
        // 2. 如果返回的位置不是零向量(Vector3.zero),则表示事件确实发生在某显示器上
        // 3. 返回的事件位置(eventPosition)的z会作为显示器索引
        // 4. 如果返回零向量,则意为多显示器系统在该平台上不受支持,在这种情况下,会假定认为是事件在当前显示器上发生
        // 5. 最终根据显示器索引获得显示器分辨率(Display.displays[index])
        // 6. 判断鼠标位置是否有超出屏幕分辨率
        int displayIndex = canvas.targetDisplay;
        var eventPosition = Display.RelativeMouseAt(eventData.position);
        if (eventPosition != Vector3.zero)
        {
            int eventDisplayIndex = (int) eventPosition.z;
            if (eventDisplayIndex != displayIndex)
            {
                return false;
            }
        }
        else
        {
            eventPosition = eventData.position;
        }

        Vector2 pos;
        if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || eventCamera == null)
        {
            float w = Screen.width;
            float h = Screen.height;
            if (displayIndex > 0 && displayIndex < Display.displays.Length)
            {
                w = Display.displays[displayIndex].systemWidth;
                h = Display.displays[displayIndex].systemHeight;
            }

            pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
        }
        else
        {
            pos = eventCamera.ScreenToViewportPoint(eventPosition);
        }

        if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
        {
            return false;
        }
        
        return true;
    }

参考资料

https://blog.csdn.net/u013477973/article/details/89343777
https://www.cnblogs.com/gwen-/p/16976033.html

标签:canvas,Graphic,遮挡,Unity,graphic,components,var,alpha
From: https://www.cnblogs.com/chenxiayun/p/18122660

相关文章

  • Unity组件
    二、Mesh网格1MeshFilterMeshFilter组件包含对网格的引用。该组件与同一个游戏对象上的MeshRenderer组件配合使用;MeshRenderer组件渲染MeshFilter组件引用的网格。用于将网格数据应用到3D模型上。它是实现3D模型的重要组成部分之一,可以定义模型的形状和结构。......
  • Unity界面
    1、场景面板(Scene):上图最左侧上半部分,该面板为Unity3D的编辑面板,用于将所需要的模型,灯光以及其他物体对象放置在面板中,构建游戏所需呈现的画面。2、游戏面板(Game):上图最左侧下半部分,该面板显示的是游戏运行时的画面,即玩家直接看到的画面,可以根据游戏面板的效果在场景面板进行相......
  • new mars3d.graphic.PolylineEntity({实现航线真实穿过山体或者模型的部分用虚线展示
    1.在官网示例中通过 newmars3d.graphic.PolylineEntity({实现航线真实穿过山体或者模型的部分用虚线展示效果2.示例地址:功能示例(Vue版)|Mars3D三维可视化平台|火星科技3.实现效果: 1.航线真实穿过山体或者模型的部分用虚线展示、并且是(真实穿过不是视线挡住那种),遮挡......
  • Unity3D代码混淆方案详解
    背景Unity引擎使用Mono运行时,而C#语言易受反编译影响,存在代码泄露风险。本文通过《QQ乐团》项目实践,提出一种适用于Unity引擎的代码混淆方案,以保护代码逻辑。引言在Unity引擎下,为了防止代码被轻易反编译,需要采取相应的保护措施。本文将分享一种基于实践经验的可行方案,希......
  • Unity3D代码混淆方案详解
    背景Unity引擎使用Mono运行时,而C#语言易受反编译影响,存在代码泄露风险。本文通过《QQ乐团》项目实践,提出一种适用于Unity引擎的代码混淆方案,以保护代码逻辑。引言在Unity引擎下,为了防止代码被轻易反编译,需要采取相应的保护措施。本文将分享一种基于实践经验的可行方案,希......
  • unity+PICO VR开发总结(二)
    一.关于点击手柄Home键,无法显示“退出应用并回到主界面”以及下方菜单栏1.打开“开发者模式”选择“企业设置” 2.选择“系统设置”里面的“Home键自定义”3.“全部”-“单击”选择“初始模式”4.再次进入程序中点击“Home键”可以显示“退出应用并回到主界面”以及下方......
  • Unity的阴影初步了解
    起因:最近学习了Unity内的实时阴影的计算,所以这里总结收录一下,加深一下印象。下面分别介绍ShadowMap和屏幕空间阴影和联级阴影的计算流程。阴影计算流程:首先获得当前摄像机观察到深度纹理。在延迟渲染中,这张深度图Unity已经帮忙计算好了,前向渲染中,我们则需要等待场景都被渲染......
  • Unity性能优化-C#篇
    Unity性能优化-C#篇 1.UnityAPI GameObject.GetComponentUnity是基于组件的开发方式,所以GetComponent是一个高频使用的函数每次调用GetComponent时,Unity都要去遍历所有的组件来找到目标组件每次都去查找是不必要的耗费,可以通过缓存的方式来避免这些不必要的开销其中......
  • Unity类银河恶魔城学习记录12-7-2 p129 Craft UI - part 2源代码
    Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibiliUI_CraftWindow.csusingUnityEngine.UI;usingTMPro;usingUnityEngine;usingU......
  • Unity编辑器中运行正常,发布后报shader为null异常问题解决方案
    在Unity中,Shader是从代码中进行加载的,编辑器中并没有引用。在编辑器中运行项目没有问题,但当项目发布到移动平台,如ios、android、UWP之后,游戏中并不能找到对应的shader。因为Shader在场景中并未被引用,所以没有被打包。解决办法1在ProjectSettings里面的Graphics,添加上修改的打包......