using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Rect Mask 2D", 13)]
[ExecuteAlways]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
///
/// A 2D rectangular mask that allows for clipping / masking of areas outside the mask.
///
///
/// The RectMask2D behaves in a similar way to a standard Mask component. It differs though in some of the restrictions that it has.
/// A RectMask2D:
/// *Only works in the 2D plane
/// *Requires elements on the mask to be coplanar.
/// *Does not require stencil buffer / extra draw calls
/// *Requires fewer draw calls
/// *Culls elements that are outside the mask area.
///
public class RectMask2D : UIBehaviour, IClipper, ICanvasRaycastFilter
{
[NonSerialized]
private readonly RectangularVertexClipper m_VertexClipper = new RectangularVertexClipper();
[NonSerialized]
private RectTransform m_RectTransform;
[NonSerialized]
private HashSet<MaskableGraphic> m_MaskableTargets = new HashSet<MaskableGraphic>();
[NonSerialized]
private HashSet<IClippable> m_ClipTargets = new HashSet<IClippable>();
[NonSerialized]
private bool m_ShouldRecalculateClipRects;
[NonSerialized]
private List<RectMask2D> m_Clippers = new List<RectMask2D>();
[NonSerialized]
private Rect m_LastClipRectCanvasSpace;
[NonSerialized]
private bool m_ForceClip;
///
/// Returns a non-destroyed instance or a null reference.
///
[NonSerialized] private Canvas m_Canvas;
private Canvas Canvas
{
get
{
if (m_Canvas == null)
{
var list = ListPool<Canvas>.Get();
gameObject.GetComponentsInParent(false, list);
if (list.Count > 0)
m_Canvas = list[list.Count - 1];
else
m_Canvas = null;
ListPool<Canvas>.Release(list);
}
return m_Canvas;
}
}
///
/// 在画布空间中获得遮罩的矩形
///
public Rect canvasRect
{
get
{
return m_VertexClipper.GetCanvasRect(rectTransform, Canvas);
}
}
///
/// 得到RectTransform
///
public RectTransform rectTransform
{
get { return m_RectTransform ?? (m_RectTransform = GetComponent<RectTransform>()); }
}
protected RectMask2D()
{}
protected override void OnEnable()
{
base.OnEnable();
m_ShouldRecalculateClipRects = true;
ClipperRegistry.Register(this); //注册为一个可裁剪元素
MaskUtilities.Notify2DMaskStateChanged(this);
}
protected override void OnDisable()
{
// we call base OnDisable first here
// as we need to have the IsActive return the
// correct value when we notify the children
// that the mask state has changed.
base.OnDisable();
m_ClipTargets.Clear();
m_MaskableTargets.Clear();
m_Clippers.Clear();
ClipperRegistry.Unregister(this);
MaskUtilities.Notify2DMaskStateChanged(this);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
m_ShouldRecalculateClipRects = true;
if (!IsActive())
return;
MaskUtilities.Notify2DMaskStateChanged(this);
}
#endif
public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (!isActiveAndEnabled)
return true;
return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera);
}
private Vector3[] m_Corners = new Vector3[4];
//获得根画布
private Rect rootCanvasRect
{
get
{
rectTransform.GetWorldCorners(m_Corners);
if (!ReferenceEquals(Canvas, null))
{
Canvas rootCanvas = Canvas.rootCanvas;
for (int i = 0; i < 4; ++i)
m_Corners[i] = rootCanvas.transform.InverseTransformPoint(m_Corners[i]);
}
return new Rect(m_Corners[0].x, m_Corners[0].y, m_Corners[2].x - m_Corners[0].x, m_Corners[2].y - m_Corners[0].y);
}
}
//执行裁剪
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); //将此RectMask2D父节点的所有RectMask2D添加到m_Clippers,包括自己
m_ShouldRecalculateClipRects = false;
}
//找到要用于裁剪的矩形。
//给定输入RectMask2ds,找到一个裁剪区域最小的矩形
bool validRect = true;
Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
// 如果遮罩是在ScreenSpaceOverlay/Camera渲染模式下,它的内容只有在它的矩形与根画布重叠时才会渲染。
RenderMode renderMode = Canvas.rootCanvas.renderMode;
bool maskIsCulled =
(renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&
!clipRect.Overlaps(rootCanvasRect, true);
if (maskIsCulled)
{
//子元素仅在遮罩内显示。如果遮罩被剔除,那么遮罩内部的孩子们也被剔除。在这种情况下,我们传入一个无效的rect来允许调用避免一些处理。
clipRect = Rect.zero;
validRect = false;
}
if (clipRect != m_LastClipRectCanvasSpace)
{
foreach (IClippable clipTarget in m_ClipTargets)
{
clipTarget.SetClipRect(clipRect, validRect); //设置裁剪,把裁切区域传到每个UI元素的Shader中
}
foreach (MaskableGraphic maskableTarget in m_MaskableTargets) //所有继承了MaskableGraphic的组件也要设置裁剪
{
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); //所有继承了MaskableGraphic的组件也要设置裁剪
if (maskableTarget.canvasRenderer.hasMoved)
maskableTarget.Cull(clipRect, validRect);
}
}
else
{
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
{
if (maskableTarget.canvasRenderer.hasMoved)
maskableTarget.Cull(clipRect, validRect);
}
}
m_LastClipRectCanvasSpace = clipRect;
m_ForceClip = false;
}
///
/// 添加一个可被RectMask2D裁剪的IClippable元素,主要是RectMask2D节点下的子元素
///
/// Add the clippable object for this mask
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;
}
///
/// Remove an IClippable from being tracked by the mask.
///
/// Remove the clippable object from this mask
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;
}
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
m_ShouldRecalculateClipRects = true; //如果父节点改变,需要重新计算裁剪
}
protected override void OnCanvasHierarchyChanged()
{
m_Canvas = null;
base.OnCanvasHierarchyChanged();
m_ShouldRecalculateClipRects = true; //canvas改变的时候,需要重新计算裁剪
}
}
}
其中m_ForceClip 强制裁剪设置主要有两种情况AddClippable和RemoveClippable的情况,都索引自MaskableGraphic类的UpdateClipParent方法
private void UpdateClipParent()
{
var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;
// 如果父节点是新的或者现在是不活动的
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;
}
这里会通过MaskUtilities.GetRectMaskForClippable寻找父物体上的RectMask2D组件,这个方法会在激活,取消激活,父节点改变,canvas改变等时候调用
PerformClipping方法索引自ClipperRegistry的Cull方法
///
/// 对所有注册的IClipper执行剪辑
///
public void Cull()
{
for (var i = 0; i < m_Clippers.Count; ++i)
{
m_Clippers[i].PerformClipping();
}
}
最终索引自CanvasUpdateRegistry的PerformUpdate方法
这里可以看到是先执行的layout重绘,在执行的裁剪重绘,最后是图形重绘