Unity开发笔记-新手引导遮罩事件穿透按钮不响应ExecuteEvents.Execute?试试ExecuteEvents.ExecuteHierarchy

点击事件穿透是新手引导中最重要的一个功能,通常做法是使用一个全屏UI。该UI放置于UI的最高层级挡住所有UI,然后监听IPointerClickHandler事件,当OnPointerClick回调触发时,通过EventSystem.current.RaycastAll获得当前点击的对象列表。
对该对象列表中的结果对象执行ExecuteEvents.Execute实现点击穿透功能。

相关代码已上传github
https://github.com/terrynoya/UnityMaskPanetrateExample

穿透功能实现#

下面实现一下该功能:

创建一个空的GameObject,挂上EmptyGraphic,使之响应点击事件,挂Image也可以同样起到作用。

Copy
public class EmptyGraphic:MaskableGraphic { }

遮罩层穿透逻辑

Copy
public class PanerateMask : MonoBehaviour,IPointerClickHandler { public void OnPointerClick(PointerEventData eventData) { Raycast(eventData); } private void Raycast(PointerEventData eventData) { _rawRaycastResults.Clear(); EventSystem.current.RaycastAll(eventData, _rawRaycastResults); foreach (var rlt in _rawRaycastResults) { Debug.Log(rlt.gameObject); //遮罩层自身需要添加该脚本,否则会导致ExecuteEvents.Execute再次触发遮罩层自身的IPointerClickHandler导致死循环 if (rlt.gameObject.GetComponent<IgnoreEventRaycast>()) { continue; } ExecuteEvents.Execute(rlt.gameObject, eventData, ExecuteEvents.pointerClickHandler); } } }

遮罩层自身需要添加该脚本,否则会导致ExecuteEvents.Execute再次触发遮罩层自身的IPointerClickHandler导致死循环

Copy
public class IgnoreEventRaycast:MonoBehaviour { }

制作按钮#

下面制作按钮进行测试

实际开发种,按钮通常有2种做法

1.Image和Button在同一个GameObject上

2.Image是Button的子节点

测试#

我们需要编写一个Test,验证按钮的OnClick是否能被触发

Copy
public class TestMain:MonoBehaviour { public Button Btn1; public Button Btn2; private void Awake() { Btn1.onClick.AddListener(OnBtn1Click); Btn2.onClick.AddListener(OnBtn2Click); } private void OnBtn1Click() { Debug.Log("btn1 clicked!!"); } private void OnBtn2Click() { Debug.Log("btn2 clicked!!"); } }

最终的层级结构如下

下面点击btn1

可以看到btn1的OnClick顺利触发了。

点击btn2,并没有出现我们期待的结果

解决方法#

将ExecuteEvents.Execute改成ExecuteEvents.ExecuteHierarchy#

测试后得到了正确的结果

原理#

我们需要看下ExcuteEvents.Exeucte做了什么

Execute内部,会收集被点击的GameObject实现的IEventSystemHandler接口列表,然后对接口进行调用。这里我们需要的是触发IPointerClickHandler的回调

代码如下:

Copy
public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler { var internalHandlers = s_HandlerListPool.Get(); GetEventList<T>(target, internalHandlers); // if (s_InternalHandlers.Count > 0) // Debug.Log("Executinng " + typeof (T) + " on " + target); for (var i = 0; i < internalHandlers.Count; i++) { T arg; try { arg = (T)internalHandlers[i]; } catch (Exception e) { var temp = internalHandlers[i]; Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e)); continue; } try { functor(arg, eventData); } catch (Exception e) { Debug.LogException(e); } } var handlerCount = internalHandlers.Count; s_HandlerListPool.Release(internalHandlers); return handlerCount > 0; }

当点击btn2时,穿透对象是Image,Image自身未实现IPointerClickHandler接口,所以Execute不起作用。
当点击btn1时,穿透对象是btn1,而btn1上挂有Button,Button实现了IPointerClickHandler接口,因此Execute起作用。

让我们看下ExecuteHierarchy#

ExecuteHierarchy中,GetEventChain函数会收集点击对象以及对象的所有父节点,对节点列表依次调用Execute,调用成功停止循环。

Copy
public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) where T : IEventSystemHandler { GetEventChain(root, s_InternalTransformList); for (var i = 0; i < s_InternalTransformList.Count; i++) { var transform = s_InternalTransformList[i]; if (Execute(transform.gameObject, eventData, callbackFunction)) return transform.gameObject; } return null; }
Copy
private static void GetEventChain(GameObject root, IList<Transform> eventChain) { eventChain.Clear(); if (root == null) return; var t = root.transform; while (t != null) { eventChain.Add(t); t = t.parent; } }

当点击btn2时,ExecuteHierarchy将Image及其所有父节点添加到Chain列表中,依次调用Excute直到成功为止。

参考连接:http://www.xuanyusong.com/archives/4241

posted @   jeoyao  阅读(1441)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 我与微信审核的“相爱相杀”看个人小程序副业
点击右上角即可分享
微信分享提示
目录