0%

RectMask2D详解

RectMaskD的基本原理就是CanvasRenderer的EnableRectClipping方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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。

1
2
3
4
5
6
7
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下的坐标值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的相交范围:

1
2
3
4
5
6
7
8
9
10
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);进行裁切。

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