UIGU源码分析14:RectMask2D

源码14:RectMask2D

前面分析MaskGraphic得时候提到了裁剪相关,这里就分析一下裁剪者组件RectMask2D

    public class RectMask2D : UIBehaviour, IClipper, ICanvasRaycastFilter
    {
        [NonSerialized]
        private readonly RectangularVertexClipper 	m_VertexClipper = new RectangularVertexClipper();
        
        private HashSet<IClippable> m_ClipTargets = new HashSet<IClippable>();
        ...
    }

RectMask2D继承了UIBehaviour, IClipper, ICanvasRaycastFilter

IClipper裁剪者 和前面讲的IClippable 是配套使用

ICanvasRaycastFilter 实现过滤接口

维护了一个IClippable类型的列表m_ClipTargets 每次子元素增加,例如一个Image组件的enable、disable。或者RectMask2D自身的状态发生改变,例如一个RectMask2D enable、disable。那么就会更新这个裁剪目标


    /// <summary>
    /// Add a IClippable to be tracked by the mask.
    /// </summary>
    /// <param name="clippable">Add the clippable object for this mask</param>
    public void AddClippable(IClippable clippable)
    {
        if (clippable == null)
            return;
        m_ShouldRecalculateClipRects = true;
        MaskableGraphic maskable = clippable as MaskableGraphic;

        if (maskable == null)
            m_ClipTargets.Add(clippable);
        else
            m_MaskableTargets.Add(maskable);

        m_ForceClip = true;
    }

设置m_ShouldRecalculateClipRects为true,把clippable类型的组件添加到m_ClipTargets列表中,设置m_ForceClip为true,这个参数会在PerformClipping被用到

调用时在MaskableGraphic 中:

private void UpdateClipParent()
        {
            var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;

            // if the new parent is different OR is now inactive
            if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive()))
            {
                m_ParentMask.RemoveClippable(this);
                UpdateCull(false);
            }

            // don't re-add it if the newparent is inactive
            if (newParent != null && newParent.IsActive())
                newParent.AddClippable(this);

            m_ParentMask = newParent;
        }

        /// <summary>
        /// Remove an IClippable from being tracked by the mask.
        /// </summary>
        /// <param name="clippable">Remove the clippable object from this mask</param>
        public void RemoveClippable(IClippable clippable)
        {
            if (clippable == null)
                return;

            m_ShouldRecalculateClipRects = true;
            clippable.SetClipRect(new Rect(), false);

            MaskableGraphic maskable = clippable as MaskableGraphic;

            if (maskable == null)
                m_ClipTargets.Remove(clippable);
            else
                m_MaskableTargets.Remove(maskable);

            m_ForceClip = true;
        }

设置m_ShouldRecalculateClipRects为true,调用clippable.SetClipRect(new Rect(), false)关闭矩形裁剪,把clippable类型的组件从m_ClipTargets列表中移除,设置m_ForceClip为true。

同样的改方法是在MaskableGraphic 中的UpdateClipParent方法中调用


 MaskUtilities.Notify2DMaskStateChanged(this)
        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);
        }

当RectMask2D组件本身有变化的的时候 会通知子物体IClippable 进行更新:调用UpdateClipParent 方法 最终就是调用到了上面的两个函数


作为IClipper 必须实现PerformClipping()方法 来实现裁剪效果

    public virtual void PerformClipping()
    {
        if (ReferenceEquals(Canvas, null))
        {
            return;
        }

        //TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771)

        // if the parents are changed
        // or something similar we
        // do a recalculate here
        if (m_ShouldRecalculateClipRects)
        {
            MaskUtilities.GetRectMasksForClip(this, m_Clippers);
            m_ShouldRecalculateClipRects = false;
        }

        // get the compound rects from
        // the clippers that are valid
        bool validRect = true;
        Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);

        // If the mask is in ScreenSpaceOverlay/Camera render mode, its content is only rendered when its rect
        // overlaps that of the root canvas.
        RenderMode renderMode = Canvas.rootCanvas.renderMode;
        bool maskIsCulled =
            (renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&
            !clipRect.Overlaps(rootCanvasRect, true);

        if (maskIsCulled)
        {
            // Children are only displayed when inside the mask. If the mask is culled, then the children
            // inside the mask are also culled. In that situation, we pass an invalid rect to allow callees
            // to avoid some processing.
            clipRect = Rect.zero;
            validRect = false;
        }

        if (clipRect != m_LastClipRectCanvasSpace)
        {
            foreach (IClippable clipTarget in m_ClipTargets)
            {
                clipTarget.SetClipRect(clipRect, validRect);
            }

            foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
            {
                maskableTarget.SetClipRect(clipRect, validRect);
                maskableTarget.Cull(clipRect, validRect);
            }
        }
        else if (m_ForceClip)
        {
            foreach (IClippable clipTarget in m_ClipTargets)
            {
                clipTarget.SetClipRect(clipRect, validRect);
            }

            foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
            {
                maskableTarget.SetClipRect(clipRect, validRect);

                if (maskableTarget.canvasRenderer.hasMoved)
                    maskableTarget.Cull(clipRect, validRect);
            }
        }
        else
        {
            foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
            {
                //Case 1170399 - hasMoved is not a valid check when animating on pivot of the object
                maskableTarget.Cull(clipRect, validRect);
            }
        }

        m_LastClipRectCanvasSpace = clipRect;
        m_ForceClip = false;

        UpdateClipSoftness();
    }
  • 如果m_ShouldRecalculateClipRects为true,会调用 MaskUtilities.GetRectMasksForClip,把本对象和父对象中所有有效的RectMask2D,放入到m_Clippers中。
  • Clipping的FindCullAndClipWorldRect方法,遍历m_Clippers的canvasRect,取交集,取得一个最小的裁剪区域,如果这个裁剪区域不合理,validRect便为false,否则根据裁剪区域,返回一个Rect,同时设置validRect为true。
  • 新的clipRect如果与之前的不同,或者m_ForceClip为true,遍历m_ClipTargets,调用 clipTarget.SetClipRect,为他们设置裁剪区域,记录新的clipRect和validRect。
  • 最后,遍历m_ClipTargets,调用所有IClippable的Cull剔除方法。

在RectMask2D 激活和关闭的时候 分别调用了

ClipperRegistry.Register(this); 和ClipperRegistry.Unregister(this);

ClipperRegistry 实际上时记录了所有激活的IClipper

在IClipper 中记录了 记录了所有需要裁剪的IClippable

最终执行裁剪的是由CanvasUpdateRegistry 中每帧执行的PerformUpdate 进行触发 ClipperRegistry.instance.Cull();

public class ClipperRegistry
{
static ClipperRegistry s_Instance;

readonly IndexedSet<IClipper> m_Clippers = new IndexedSet<IClipper>();

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

猜你喜欢

转载自blog.csdn.net/NippyLi/article/details/123599902