以前 Unmask の記事を書きましたが、Unmask したからには
その部分のクリックをしたいと思ったのでクリック処理を追加してみました
ICanvasRaycastFilter
を使う方法もありますが、Mask内にボタンを設置する必要があったので
利用しづらいと考え別の方法で実装しました
動画でも画像でもわからない感じだったので Unmask している画像だけ貼っておきます
仕組み
IPointerClickHandler
を追加して、Unmask 部分をクリック可能にする
public class UnMask : Mask, IPointerClickHandler void IPointerClickHandler.OnPointerClick(PointerEventData eventData) {
EventSystem.current.RaycastAll
より クリックした場所から再度 Ray を飛ばして
Raycast が ヒットするオブジェクト一覧を取得する
この際、当たる順番に取得できるので、先頭から順番に Mask じゃないところまでループさせる
var hits = new List<RaycastResult>(); EventSystem.current.RaycastAll(eventData, hits); if (hits.Count <= 0) return; GameObject hitObj = null; foreach (var hit in hits) { if (HasInParent(hit.gameObject.transform, _rootMask.transform)) continue; hitObj = FindParentIEventSystemObject(hit.gameObject); break; }
ヒットした場合そのオブジェクトが Unmask が無い場合の対象になるので
そのオブジェクトに対して、クリック処理を無理やり実行する
これにより擬似的にクリックしたことにできる
もっとクリック感を出したい場合は、以下の Interface を追加して正しく伝播すればできると思います
ExecuteEvents.Execute<IPointerEnterHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerEnter((PointerEventData)ev)); ExecuteEvents.Execute<IPointerDownHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerDown((PointerEventData)ev)); ExecuteEvents.Execute<IPointerClickHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerClick((PointerEventData)ev)); ExecuteEvents.Execute<IPointerUpHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerUp((PointerEventData)ev)); ExecuteEvents.Execute<IPointerExitHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerExit((PointerEventData)ev));
配置
オブジェクトの配置や Raycast に関しては適当でもできるようにしたかったのですが、正しく設定しないと動作しない
Canvas<br> ├ Button (Button) └ Mask (Mask, Image, Raycast: true) ├ Unmask (Unmask, Image, Raycast: true) └ FadeImage (Image, Raycast: false)
コード
using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Rendering; using UnityEngine.UI; public class UnMask : Mask, IPointerClickHandler { [NonSerialized] private Material _maskMaterial; [SerializeField] private Mask _rootMask; protected override void OnDisable() { if (graphic != null) { graphic.SetMaterialDirty(); if (graphic is MaskableGraphic) ((MaskableGraphic) graphic).isMaskingGraphic = false; } StencilMaterial.Remove(_maskMaterial); _maskMaterial = null; MaskUtilities.NotifyStencilStateChanged(this); } public override Material GetModifiedMaterial(Material baseMaterial) { if (!MaskEnabled()) return baseMaterial; var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas); if (stencilDepth >= 8) { Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject); return baseMaterial; } var desiredStencilBit = 1 << stencilDepth; var maskMaterial = StencilMaterial.Add( baseMaterial, desiredStencilBit - 1, StencilOp.Zero, CompareFunction.Always, showMaskGraphic ? ColorWriteMask.All : 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1) ); StencilMaterial.Remove(_maskMaterial); _maskMaterial = maskMaterial; return _maskMaterial; } void IPointerClickHandler.OnPointerClick(PointerEventData eventData) { var hits = new List<RaycastResult>(); EventSystem.current.RaycastAll(eventData, hits); if (hits.Count <= 0) return; GameObject hitObj = null; foreach (var hit in hits) { if (HasInParent(hit.gameObject.transform, _rootMask.transform)) continue; hitObj = FindParentIEventSystemObject(hit.gameObject); break; } if (hitObj == null) return; ExecuteEvents.Execute<IPointerEnterHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerEnter((PointerEventData)ev)); ExecuteEvents.Execute<IPointerDownHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerDown((PointerEventData)ev)); ExecuteEvents.Execute<IPointerClickHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerClick((PointerEventData)ev)); ExecuteEvents.Execute<IPointerUpHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerUp((PointerEventData)ev)); ExecuteEvents.Execute<IPointerExitHandler>(hitObj, eventData, (handler, ev) => handler.OnPointerExit((PointerEventData)ev)); } private static bool HasInParent(Transform transform, Transform target) { var current = transform; while (current.parent != null) { if (current == target) return true; current = current.parent; } return false; } private GameObject FindParentIEventSystemObject(GameObject gameObject) { var current = gameObject.transform; while (current != null) { var target = current.gameObject.GetComponent<IEventSystemHandler>(); if (target != null) return current.gameObject; current = current.parent; } return null; } } [CustomEditor(typeof(UnMask))] public class UnmaskEditor : Editor { private SerializedProperty _script; private SerializedProperty _rootMask; private void OnEnable() { _script = serializedObject.FindProperty("m_Script"); _rootMask = serializedObject.FindProperty("_rootMask"); } public override void OnInspectorGUI() { serializedObject.UpdateIfRequiredOrScript(); using (new EditorGUI.DisabledScope(true)) { EditorGUILayout.PropertyField(_script); } EditorGUILayout.PropertyField(_rootMask); serializedObject.ApplyModifiedProperties(); } }