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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM