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顺利触发了。

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

解决方法

将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直到成功为止。

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

posted @ 2022-02-07 10:15  jeoyao  阅读(1204)  评论(0编辑  收藏  举报