1.前言
RectMaskD的基本原理就是CanvasRenderer的EnableRectClipping方法,上一節已經做了詳細說明。而它的工作流程在(六)和(五)中也做了詳細分析。此篇重新梳理一下流程,做更細致的分析。
2.詳解
RectMask2D的基本原理比較建議,復雜點在於其上層邏輯比較復雜,今天就按邏輯順序進行分析。
1)啟動時通過ClipperRegistry.Register(this);將自己注冊到RectMask2D的管理類ClipperRegistry中,便於后續統一調用(CLipperRegistry參與整個Canvas的運作流程,所以可以參考此文的流程圖以及2.3節的分析)。
2)啟動的同時通過 MaskUtilities.Notify2DMaskStateChanged(this)通知所有子游戲物體(繼承IClippable,后續簡稱子Clippable)重新更新Clipp狀態(通過UpdateClipParent重新確定影響自身Clip的RectMask2D);由於考慮到會存在多個Canvas以及RectMask2D的情況,所以子Clippable在得到重新更新狀態通知時,會調用MaskUtilities.GetRectMaskForClippable方法重新確認RectMask2D。確認后每個子Clippable將自己添加到相應的RectMask2D維護的列表中。
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。
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下的坐標值)。
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的相交范圍:
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);進行裁切。
以上為基本流程,真是代碼中會考慮其他一些狀況。比如第五步並非一定會進行裁切,而是會根據條件選擇裁切或者不進行裁切。
3.結語
以上為RectMask2D裁切的詳細流程分析。