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官方的解决方法)