从UGUI源码中想到的UI动态”挖洞”效果的实现(蒙版过滤)

一、需求描述:

项目中有时候会用到一些“”挖洞“”效果,比如新手教程出现时,整个画面变暗(蒙版),但是突出突出个别的按钮,供玩家点击,但是别的UI元素不能响应点击事件。常见的做法可能是在最上层加一个蒙版,然后改变需要突出的按钮的层级。这种方法对于层级关系简单的项目还可以勉强使用,但是对于层级关系比较复杂而又有其他的限制的时候就有点捉襟见肘了。这里介绍另一种方法:使用模板测试,可以应对很复杂的情况。

效果样例(在层级关系不变的情况下改变显示结果):

图一:没有“挖洞“效果的时候:俩按钮和一个图片都被蒙版覆盖变暗

从UGUI源码中想到的UI动态”挖洞”效果的实现(蒙版过滤)_第1张图片

图二:让两个button显示在蒙版之前,image被蒙版覆盖从UGUI源码中想到的UI动态”挖洞”效果的实现(蒙版过滤)_第2张图片

图三:显示button2和image

从UGUI源码中想到的UI动态”挖洞”效果的实现(蒙版过滤)_第3张图片

用法:把mask的位置放置到合适的地方(比如最后渲染的地方),挂上修改后的Mask组件HoleMask.cs,然后修改其StencilValue值,然后再需要显示的按钮或者图片等其他图形上挂CustomStencilImag.cs,设置其StencilValue值,并确保其比HoleMask的StencilValue大即可。

二、一些必要的知识点:

如果想完全掌握这种方法并灵活运用,建议了解以下知识点:

(1)模板测试技术基础

(2)UGUI源码的获取

(3)对接口的理解

我觉得熟悉UGUI源码是一件非常有益的事情,你可以通过这些源码了解平时常用的各种组件的原理,并根据公司或者项目的实际情况进行改进。有时候有些复杂的问题可能看看源码就可能想到很简单的方法去实现,从而在工作效率、程序运行效率上有所提高,达到意想不到的效果。

对于以2D为主的项目,我们主要是与图片和按钮打交道。而基本上所有UI的东西都是由MaskableGraphic派生而来的。而MaskableGraphic的含义就是可遮罩的图形元素,它又是由Graphic派生而来的。既然大多UI元素都派生自MaskableGraphic,那么必然会有一个产生遮罩的组件,那就是Mask(或rectMask2D,二者的异同官方手册有明确对比)。而Mask最核心的实现技术就是模板测试技术。Mask能够对图片产生遮罩裁剪效果的原理就是Mask组件所在的GameObject上的图片组件(如Image)在屏幕上所占的每一个像素位置,都统一保存了一个数字,这个数字保存在模板缓冲区中,一般是8位的,所以数字的范围只能是0~255。而Mask物体下的所有派生自MaskableGraphic的组件(如Image)都会在自身或者Mask发生改变时重新计算自己的这个数字值(称之为参考值,计算的结果就是自身的数字值会与自己最“亲近”的“长辈”Mask的模板缓冲区的值一样),只有在Mask区域内的部分才会被渲染出来,这方面即是模板测试技术的相关知识,读者可以自行去查官方手册,很简单的,建议做一下官方的例子,容易理解和记忆。下面就细说说Mask组件的源码。Mask组件继承了UIBehaviour组件、ICanvasRaycastFilter接口、 IMaterialModifier接口。UIBehaviour组件是每一个UI基本元素都会继承的一个基础类,其内定义了UI元素产生变化时的各种响应函数。ICanvasRaycastFilter接口要求子类必须实现一个ICanvasRaycastFilter函数,该函数接受两个参数,一个是屏幕位置,一个是参与事件的摄像机组件。我们只需要把自带的Mask源码复制一份,然后改写一下这两个接口的实现函数即可。改写后的函数如下:

 	public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
        {
            if (!isActiveAndEnabled)
                return true;
            CustomStencilImag[] imgs = GameObject.FindObjectsOfType();
            for (int i = 0; i < imgs.Length; i++)
            {
                if (imgs[i].enabled&&RectTransformUtility.RectangleContainsScreenPoint(imgs[i].transform as RectTransform, sp, eventCamera))
                {
                    return false;
                }
            }
            return true;
        }
        /// Stencil calculation time!
        public virtual Material GetModifiedMaterial(Material baseMaterial)
        {
            if (!MaskEnabled())
                return baseMaterial;
            int desiredStencilBit = stencilValue;
            var maskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit , StencilOp.Keep, CompareFunction.Greater, m_ShowMaskGraphic ? ColorWriteMask.All : 0, 255, 255);
            StencilMaterial.Remove(m_MaskMaterial);
            m_MaskMaterial = maskMaterial2;
            return m_MaskMaterial;
        }
GetModifiedMaterial函数用来确保正确的显示效果,它使用本物体上的图形组件的材质(默认的或者自己拖进去的)克隆出一份新的材质给CanvasRenderer使用,并且这个材质的模板测试的参考值讲设置为我们自己设定的(而官方自带的是使用另一种方法算出来的,其值=2的N次方减1,N是从本物体及以上祖先物体中Mask组件的层数,由于模板缓冲区是8位的,这就注定了Mask最多使用8层,否则会出问题)。

在IsRaycastLocationValid确保了点击事件的触发,使得按钮不会因为被蒙版挡住而不能被射线检测到。这个函数根据字面意思就是“是否射线定位有效",在谁身上有效呢?当然是自己(蒙版)了。有效即视为射线击中自己,不再向下检测,否则穿透蒙版继续向下检测。官方源码中只要判断自身是激活可用的,并且射线位置在自身rect内即可认定有效,而我们改写的是只要射线位置在任意一个CustomStencilImag的rect范围内即认定无效,从而穿透蒙版检测到我们需要响应点击的按钮。



你可能感兴趣的:(u3d相关)