Unity -点击下传

问题

策划需求,打开道具提示浮动弹窗时,点击空白处,需要关闭弹窗,并且其他组件要响应点击操作。

解决

基本思路是,背景遮罩响应到点击时间时,执行关闭弹窗,然后再把点击事件从当前节点继续往下发。
以下代码来自雨松大佬,项目里是用另外的实现方式(直接封装在了按钮组件里面),但原理都是一样的,就不贴项目代码了,不值一贴。
但是有个特殊的地方,button节点即使没有接收点击的组件也能响应,只要子节点有接收点击的组件,就会自动触发到button的点击事件。区别在于ExcuteEvents.cs中的ExecuteHierarchy和Execute,这两个方法的区别是ExecuteHierarchy会获取父节点组件的事件Handler(通过GetEventHandler),而Execute只会获取当前节点组件的事件Handler。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
 
public class UITouchPass : MonoBehaviour, IPointerClickHandler,
    IMoveHandler,IPointerDownHandler, IPointerUpHandler,IPointerEnterHandler,ISelectHandler, IDeselectHandler
    , ISubmitHandler, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
{
    public virtual void OnPointerClick(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.pointerClickHandler);
    }
    public virtual void OnPointerDown(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.pointerDownHandler);
        if (Input.GetButtonDown("Submit"))
           ExecuteEvents.Execute(eventData.pointerCurrentRaycast.gameObject, eventData, ExecuteEvents.submitHandler);
    }
 
    public virtual void OnPointerUp(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.pointerUpHandler);
    }
 
    public virtual void OnPointerEnter(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.pointerEnterHandler);
    }
 
    public virtual void OnSelect(BaseEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.selectHandler);
    }
 
    public virtual void OnDeselect(BaseEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.deselectHandler);
    }
 
    public virtual void OnSubmit(BaseEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.submitHandler);
    }
 
    public virtual void OnMove(AxisEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.moveHandler);
    }
 
    GameObject CacheGameObject;
    public virtual void OnInitializePotentialDrag(PointerEventData eventData)
    {
        CacheGameObject = PassEvent(eventData, ExecuteEvents.initializePotentialDrag);
    }
    public virtual void OnBeginDrag(PointerEventData eventData)
    {
         PassEvent(eventData, ExecuteEvents.beginDragHandler);
    }
    public virtual void OnDrag(PointerEventData eventData)
    {
        ExecuteEvents.Execute(CacheGameObject, eventData, ExecuteEvents.dragHandler);
    }
    public virtual void OnEndDrag(PointerEventData eventData)
    {
        ExecuteEvents.Execute(CacheGameObject, eventData, ExecuteEvents.endDragHandler);
        CacheGameObject = null;
    }
    public virtual void OnScroll(PointerEventData eventData)
    {
        ExecuteEvents.Execute(CacheGameObject, eventData, ExecuteEvents.scrollHandler);
    }
 
    List<RaycastResult> result = new List<RaycastResult>();
    GameObject PassEvent<T>(BaseEventData data, ExecuteEvents.EventFunction<T> function) where T : IEventSystemHandler
    {
        PointerEventData eventData = data as PointerEventData;
        var pointerGo = eventData.pointerCurrentRaycast.gameObject?? eventData.pointerDrag;
        EventSystem.current.RaycastAll(eventData, result);
        foreach (var item in result)
        {
            var go = item.gameObject;
            if (go != null && go != pointerGo)
            {
                var excuteGo = ExecuteEvents.GetEventHandler<T>(go);
                if (excuteGo)
                {
                    if (excuteGo.TryGetComponent<UITouchPass>(out var __))
                        return null;
                    ExecuteEvents.Execute(excuteGo, data, function);
                    return excuteGo;
                }
                else
                {
                    if(go.TryGetComponent<UnityEngine.UI.Graphic>(out var com))
                    {
                        if (com.raycastTarget) return null;
                    }
                }
            }
        }
        return null;
    }
}

原理笔记(都在UGUI开源源码里的EventSystem中)

事件触发大致流程

组件继承 Graphic 
Graphic.OnEnable调用GraphicRegistry.RegisterGraphicForCanvas(canvas, this);注册自己和canvas关联,canvas是父节点离自己最新的激活的cavas组件
EventSystem.Update执行InputModule.Process
执行InputModule.ProcessTouchEvents
执行InputModule.GetTouchPointerEventData
执行EventSystem.RaycastAll
执行GraphicRaycaster.Raycast,根据GraphicRegistry.GetGraphicsForCanvas获得所有响应组件,判断是否相交,相交则加入列表返回,最终得到第一个响应的组件
执行InputModule.ProcessTouchPress
执行各种ExecuteEvents.Execute

ExcuteEvents.cs 阅读笔记

GetEventChain(GameObject root, IList<Transform> eventChain)
获取父节点transform列表,包括自己

GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
获取go节点上激活并且实现了IEventSystemHandler的component列表

bool ShouldSendToComponent<T>(Component component) where T : IEventSystemHandler
component是否激活(必须继承了实现了IEventSystemHandler接口类的类型)

bool Execute<T>(GameObject target, BaseEventData eventD`ata, EventFunction<T> functor) where T : IEventSystemHandler
收集当前节点component,全部执行一遍

GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) where T : IEventSystemHandler
收集当前节点的父节点,包括自己,然后依次执行Execute,直到有个节点执行成功后,返回当前执行的go

bool CanHandleEvent<T>(GameObject go) where T : IEventSystemHandler
节点上的component是否有可执行的handler

GameObject GetEventHandler<T>(GameObject root) where T : IEventSystemHandler
依次从自己往父节点寻找,直到找到一个可以执行Handler的父节点,返回父节点,否则返回null

参考

Unity3D研究院之UI完整透下事件
UGUI研究院之忽略UI被拦截事件
UGUI事件传递流程解析
Unity UGUI —— 鼠标穿透UI问题(Unity官方的解决方法)

原文地址:https://www.cnblogs.com/nickcan/p/14418138.html