RectMask2D详解

Posted llstart-new0201

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RectMask2D详解相关的知识,希望对你有一定的参考价值。

1.前言

RectMaskD的基本原理就是CanvasRenderer的EnableRectClipping方法,上一节已经做了详细说明。而它的工作流程在(六)(五)中也做了详细分析。此篇重新梳理一下流程,做更细致的分析。

2.详解

RectMask2D的基本原理比较建议,复杂点在于其上层逻辑比较复杂,今天就按逻辑顺序进行分析。
1)启动时通过ClipperRegistry.Register(this);将自己注册到RectMask2D的管理类ClipperRegistry中,便于后续统一调用(CLipperRegistry参与整个Canvas的运作流程,所以可以参考此文的流程图以及2.3节的分析)。
2)启动的同时通过 MaskUtilities.Notify2DMaskStateChanged(this)通知所有子游戏物体(继承IClippable,后续简称子Clippable)重新更新Clipp状态(通过UpdateClipParent重新确定影响自身Clip的RectMask2D);由于考虑到会存在多个Canvas以及RectMask2D的情况,所以子Clippable在得到重新更新状态通知时,会调用MaskUtilities.GetRectMaskForClippable方法重新确认RectMask2D。确认后每个子Clippable将自己添加到相应的RectMask2D维护的列表中。

        public static void Notify2DMaskStateChanged(Component mask)
        {
            var components = ListPool<Component>.Get();
            mask.GetComponentsInChildren(components);
            for (var i = 0; i < components.Count; i++)
            {
                if (components[i] == null || components[i].gameObject == mask.gameObject)
                    continue;

                var toNotify = components[i] as IClippable;
                if (toNotify != null)
                    toNotify.RecalculateClipping();
            }
            ListPool<Component>.Release(components);
        }

以上两步为逻辑层控制实现子游戏物体mask的基础。后续是实现mask的方法。


3)当Canvas更新时会调用ClipperRegistry的cull方法进行剔除(即实现遮罩),如下所示。cull方法会通知所有的RectMask2D进行PerformClipping。

       public void Cull()
        {
            for (var i = 0; i < m_Clippers.Count; ++i)
            {
                m_Clippers[i].PerformClipping();
            }
        }

4)当RectMask2D收到PerformClipping命令时,先获取所有父类有效的RectMask2D。这是为了后续计算遮罩的范围Rect。因为当有两个RectMask2D时,裁切范围是两个共同作用的区域。然后采用Clipping.FindCullAndClipWorldRect方法计算裁切区域。通过名字也可以知道,计算出来的rect为world级别的(其实就是对应的Canvas下的坐标值)。

       public static Rect FindCullAndClipWorldRect(List<RectMask2D> rectMaskParents, out bool validRect)
        {
            if (rectMaskParents.Count == 0)
            {
                validRect = false;
                return new Rect();
            }

            var compoundRect = rectMaskParents[0].canvasRect;
            for (var i = 0; i < rectMaskParents.Count; ++i)
                compoundRect = RectIntersect(compoundRect, rectMaskParents[i].canvasRect);

            var cull = compoundRect.width <= 0 || compoundRect.height <= 0;
            if (cull)
            {
                validRect = false;
                return new Rect();
            }

            Vector3 point1 = new Vector3(compoundRect.x, compoundRect.y, 0.0f);
            Vector3 point2 = new Vector3(compoundRect.x + compoundRect.width, compoundRect.y + compoundRect.height, 0.0f);
            validRect = true;
            return new Rect(point1.x, point1.y, point2.x - point1.x, point2.y - point1.y);
        }

其中比较有用的一个方法是计算两个rect的相交范围:

        private static Rect RectIntersect(Rect a, Rect b)
        {
            float xMin = Mathf.Max(a.x, b.x);
            float xMax = Mathf.Min(a.x + a.width, b.x + b.width);
            float yMin = Mathf.Max(a.y, b.y);
            float yMax = Mathf.Min(a.y + a.height, b.y + b.height);
            if (xMax >= xMin && yMax >= yMin)
                return new Rect(xMin, yMin, xMax - xMin, yMax - yMin);
            return new Rect(0f, 0f, 0f, 0f);
        }

5)当确定了裁切范围后,RectMask2D通知自己维护的IClippable列表成员进行裁切,然后每个IClippable列表成员调用 canvasRenderer.EnableRectClipping(clipRect);进行裁切。

以上为基本流程,真是代码中会考虑其他一些状况。比如第五步并非一定会进行裁切,而是会根据条件选择裁切或者不进行裁切。

3.结语

以上为RectMask2D裁切的详细流程分析。

以上是关于RectMask2D详解的主要内容,如果未能解决你的问题,请参考以下文章

Unity2017.1官方UGUI文档翻译——RectMask2D

UGUI源码解析——RectMask2D

Mask详解

Unity Mask 和RectMask2D原理和区别

(转) Java中的负数及基本类型的转型详解

详解Android WebView加载html片段