• 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

  • 相关阅读:
    07_zookeeper的客户端工具curator_基本api
    06_zookeeper原生Java API使用
    05_zookeeper的ACL
    04_zookeeper的watcher机制
    03_Zookeeper基本数据模型及基本命令操作
    02_zookeeper配置
    01_Zookeeper简述
    thinkphp之url的seo优化
    thinkphp Upload上传文件在客户端生成的临时文件$_FILES['file']['tmp_name']
    php过滤表单输入的emoji表情
  • 原文地址:https://www.cnblogs.com/terrynoya/p/15850557.html
Copyright © 2020-2023  润新知