106 lines
3.7 KiB
C#
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;
|
|
}
|
|
}
|