Files

106 lines
3.7 KiB
C#

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
/// <summary>
/// Inverted stencil mask (see materialForRendering). Raycast options live on
/// <see cref="CutoutMaskUISettings"/> on the same GameObject (Unity's Image
/// inspector does not show fields on Image subclasses).
///
/// Parent <see cref="Mask"/> also implements <see cref="ICanvasRaycastFilter"/> and only
/// allows points inside its RectTransform. A fullscreen child under a small mask would
/// otherwise fail the raycast outside that rect (dimmed area lets clicks through) while
/// still blocking inside the mask bounds. We skip only Mask/RectMask2D in the raycast
/// walk (<see cref="Graphic.Raycast(UnityEngine.Vector2,UnityEngine.Camera,bool)"/> with
/// ignoreMasks) so hit testing matches the fullscreen Image; rendering still uses stencil
/// because <see cref="MaskableGraphic.maskable"/> stays true.
/// </summary>
[RequireComponent(typeof(CutoutMaskUISettings))]
public class CutoutMaskUI : Image
{
CutoutMaskUISettings settings;
Mask cachedParentMask;
Graphic cachedMaskGraphic;
bool storedMaskGraphicRaycast;
bool storedMaskGraphicRaycastValid;
public override bool Raycast(Vector2 sp, Camera eventCamera)
{
return Raycast(sp, eventCamera, ignoreMasks: true);
}
public override Material materialForRendering
{
get
{
Material material = new Material(base.materialForRendering);
material.SetInt("_StencilComp", (int)CompareFunction.NotEqual);
return material;
}
}
protected override void OnEnable()
{
settings = GetComponent<CutoutMaskUISettings>();
base.OnEnable();
if (settings == null || !settings.disableRaycastOnParentMaskGraphic)
return;
cachedParentMask = GetComponentInParent<Mask>();
if (cachedParentMask == null)
return;
cachedMaskGraphic = cachedParentMask.graphic;
if (cachedMaskGraphic == null || cachedMaskGraphic == this)
return;
storedMaskGraphicRaycastValid = true;
storedMaskGraphicRaycast = cachedMaskGraphic.raycastTarget;
cachedMaskGraphic.raycastTarget = false;
}
protected override void OnDisable()
{
if (storedMaskGraphicRaycastValid && cachedMaskGraphic != null)
{
cachedMaskGraphic.raycastTarget = storedMaskGraphicRaycast;
storedMaskGraphicRaycastValid = false;
}
cachedMaskGraphic = null;
cachedParentMask = null;
base.OnDisable();
}
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
if (IsInsidePassThroughHole(screenPoint, eventCamera))
return settings != null && settings.blockRaycastThroughCutout;
return base.IsRaycastLocationValid(screenPoint, eventCamera);
}
bool IsInsidePassThroughHole(Vector2 screenPoint, Camera eventCamera)
{
RectTransform[] passThroughRects = settings != null ? settings.raycastPassThroughRects : null;
if (passThroughRects != null)
{
for (int i = 0; i < passThroughRects.Length; i++)
{
RectTransform rect = passThroughRects[i];
if (rect != null && RectTransformUtility.RectangleContainsScreenPoint(rect, screenPoint, eventCamera))
return true;
}
}
Mask mask = cachedParentMask != null ? cachedParentMask : GetComponentInParent<Mask>();
if (mask != null && mask.rectTransform != null &&
RectTransformUtility.RectangleContainsScreenPoint(mask.rectTransform, screenPoint, eventCamera))
return true;
return false;
}
}