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也可以同样起到作用。
public class EmptyGraphic:MaskableGraphic
{
}
遮罩层穿透逻辑
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导致死循环
public class IgnoreEventRaycast:MonoBehaviour
{
}
制作按钮#
下面制作按钮进行测试
实际开发种,按钮通常有2种做法
1.Image和Button在同一个GameObject上
2.Image是Button的子节点
测试#
我们需要编写一个Test,验证按钮的OnClick是否能被触发
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顺利触发了。
解决方法#
将ExecuteEvents.Execute改成ExecuteEvents.ExecuteHierarchy#
测试后得到了正确的结果
原理#
我们需要看下ExcuteEvents.Exeucte做了什么
Execute内部,会收集被点击的GameObject实现的IEventSystemHandler接口列表,然后对接口进行调用。这里我们需要的是触发IPointerClickHandler的回调
代码如下:
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,调用成功停止循环。
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;
}
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直到成功为止。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 我与微信审核的“相爱相杀”看个人小程序副业