UIGU source code analysis 14: RectMask2D

Source code 14: RectMask2D

When analyzing MaskGraphic earlier, we mentioned cropping. Here we will analyze the cropper component RectMask2D.

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

RectMask2D inherits UIBehaviour, IClipper, ICanvasRaycastFilter

IClipper is used in conjunction with the IClippable mentioned earlier.

ICanvasRaycastFilter implements the filtering interface

A list of IClippable type m_ClipTargets is maintained. Each time a child element is added, such as enable and disable of an Image component. Or the state of RectMask2D itself changes, such as a RectMask2D enable or disable. Then the cropping target will be updated


    /// <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;
    }

Set m_ShouldRecalculateClipRects to true, add the clippable type component to the m_ClipTargets list, and set m_ForceClip to true. This parameter will be used in PerformClipping.

In MaskableGraphic when called:

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;
        }

Set m_ShouldRecalculateClipRects to true, call clippable.SetClipRect(new Rect(), false) to turn off rectangular clipping, remove the clippable type component from the m_ClipTargets list, and set m_ForceClip to true.

The same modification method is called in the UpdateClipParent method in MaskableGraphic


 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);
        }

When the RectMask2D component itself changes, the sub-object IClippable will be notified to update: calling the UpdateClipParent method will ultimately call the above two functions.


As an IClipper, you must implement the PerformClipping() method to achieve the clipping effect.

    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();
    }
  • If m_ShouldRecalculateClipRects is true, MaskUtilities.GetRectMasksForClip will be called to put all valid RectMask2Ds in this object and the parent object into m_Clippers.
  • Clipping's FindCullAndClipWorldRect method traverses the canvasRect of m_Clippers, takes the intersection, and obtains a minimum clipping area. If the clipping area is unreasonable, validRect will be false. Otherwise, a Rect will be returned based on the clipping area, and validRect will be set to true.
  • If the new clipRect is different from the previous one, or m_ForceClip is true, traverse m_ClipTargets, call clipTarget.SetClipRect, set the cropping area for them, and record the new clipRect and validRect.
  • Finally, traverse m_ClipTargets and call the Cull elimination methods of all IClippables.

Called when RectMask2D is activated and closed respectively.

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

ClipperRegistry actually records all activated IClipper in real time

All IClippables that need to be clipped are recorded in IClipper.

The final execution of clipping is triggered by PerformUpdate executed every frame in CanvasUpdateRegistry. 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();
        }
    }
    ...
}

Guess you like

Origin blog.csdn.net/NippyLi/article/details/123599902