源码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();
}
}
...
}