using System; using UnityEngine.Events; using UnityEngine.Rendering; namespace UnityEngine.UI { public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier { [NonSerialized] protected bool m_ShouldRecalculateStencil = true; [NonSerialized] protected Material m_MaskMaterial; [NonSerialized] private RectMask2D m_ParentMask; // m_Maskable is whether this graphic is allowed to be masked or not. It has the matching public property maskable. // The default for m_Maskable is true, so graphics under a mask are masked out of the box. // The maskable property can be turned off from script by the user if masking is not desired. // m_IncludeForMasking is whether we actually consider this graphic for masking or not - this is an implementation detail. // m_IncludeForMasking should only be true if m_Maskable is true AND a parent of the graphic has an IMask component. // Things would still work correctly if m_IncludeForMasking was always true when m_Maskable is, but performance would suffer. [NonSerialized] private bool m_Maskable = true; [NonSerialized] [Obsolete("Not used anymore.", true)] protected bool m_IncludeForMasking = false; [Serializable] public class CullStateChangedEvent : UnityEvent {} // Event delegates triggered on click. [SerializeField] private CullStateChangedEvent m_OnCullStateChanged = new CullStateChangedEvent(); public CullStateChangedEvent onCullStateChanged { get { return m_OnCullStateChanged; } set { m_OnCullStateChanged = value; } } public bool maskable { get { return m_Maskable; } set { if (value == m_Maskable) return; m_Maskable = value; m_ShouldRecalculateStencil = true; SetMaterialDirty(); } } [NonSerialized] [Obsolete("Not used anymore", true)] protected bool m_ShouldRecalculate = true; [NonSerialized] protected int m_StencilValue; public virtual Material GetModifiedMaterial(Material baseMaterial) { var toUse = baseMaterial; if (m_ShouldRecalculateStencil) { var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); // Graphic在masks下的深度,如果不是0且没有mask组件意味着是普通的非mask用graphic(如果是0一定是mask用的graphic) m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0; m_ShouldRecalculateStencil = false; } // if we have a enabled Mask component then it will // generate the mask material. This is an optimisation // it adds some coupling between components though :( Mask maskComponent = GetComponent(); if (m_StencilValue > 0 && (maskComponent == null || !maskComponent.IsActive())) { // Ref = (1 << stencilValue) - 1 // Op = Keep // Func = Equal // ReadMask = (1 << stencilValue) - 1 // WriteMask = 0 var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0); StencilMaterial.Remove(m_MaskMaterial); m_MaskMaterial = maskMat; toUse = m_MaskMaterial; } return toUse; } public virtual void Cull(Rect clipRect, bool validRect) { var cull = !validRect || !clipRect.Overlaps(rootCanvasRect, true); UpdateCull(cull); } private void UpdateCull(bool cull) { var cullingChanged = canvasRenderer.cull != cull; canvasRenderer.cull = cull; if (cullingChanged) { UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this); m_OnCullStateChanged.Invoke(cull); SetVerticesDirty(); // 这里需要更新一下canvasRenderer的网格数据 } } public virtual void SetClipRect(Rect clipRect, bool validRect) { if (validRect) canvasRenderer.EnableRectClipping(clipRect); else canvasRenderer.DisableRectClipping(); } protected override void OnEnable() { base.OnEnable(); m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); if (GetComponent() != null) { MaskUtilities.NotifyStencilStateChanged(this); } } protected override void OnDisable() { base.OnDisable(); m_ShouldRecalculateStencil = true; SetMaterialDirty(); UpdateClipParent(); StencilMaterial.Remove(m_MaskMaterial); m_MaskMaterial = null; if (GetComponent() != null) { MaskUtilities.NotifyStencilStateChanged(this); } } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); } #endif protected override void OnTransformParentChanged() { base.OnTransformParentChanged(); if (!isActiveAndEnabled) return; m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); } [Obsolete("Not used anymore.", true)] public virtual void ParentMaskStateChanged() {} protected override void OnCanvasHierarchyChanged() { base.OnCanvasHierarchyChanged(); if (!isActiveAndEnabled) return; m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); } readonly Vector3[] m_Corners = new Vector3[4]; private Rect rootCanvasRect { get { rectTransform.GetWorldCorners(m_Corners); if (canvas) { 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); } } 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; } public virtual void RecalculateClipping() { UpdateClipParent(); } public virtual void RecalculateMasking() { m_ShouldRecalculateStencil = true; SetMaterialDirty(); } } }