赞
踩
策划提了个需求,具体为给游戏内的所有按钮都加一个0.5s的公CD
笨方法是写个脚本给所有的Button和Toggle挂上,然后控制点击事件的响应
但是这样太麻烦了,要改动的太多了,假如我能拦截所有的点击事件,不就可以统一控制了
于是去查询了一下Unity点击事件的实现
发现都是基于IPointerClickHandler接口,凡是实现了该接口的对象都会触发点击事件
public interface IPointerClickHandler : IEventSystemHandler
{
void OnPointerClick(PointerEventData eventData);
}
继续查在哪调用的,从EventSystem里面找到了BaseInputModule
private BaseInputModule m_CurrentInputModule;
这玩意就是跟EventSystem挂载一起的StandaloneInputModule继承的基类
然后看StandaloneInputModule里面的代码,全局查找IPointerClickHandler
private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); GameObject eventHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo); if (pointerEvent.pointerClick == eventHandler && pointerEvent.eligibleForClick) { ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler); } if (pointerEvent.pointerDrag != null && pointerEvent.dragging) { ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler); } pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = null; pointerEvent.pointerClick = null; if (pointerEvent.pointerDrag != null && pointerEvent.dragging) { ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler); } pointerEvent.dragging = false; pointerEvent.pointerDrag = null; if (currentOverGo != pointerEvent.pointerEnter) { HandlePointerExitAndEnter(pointerEvent, null); HandlePointerExitAndEnter(pointerEvent, currentOverGo); } m_InputPointerEvent = pointerEvent; }
大概就是在释放鼠标的时候会检查一次点击事件,仔细看核心代码就一句
ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
内部代码
public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler { List<IEventSystemHandler> list = s_HandlerListPool.Get(); GetEventList<T>(target, list); int count = list.Count; for (int i = 0; i < count; i++) { T handler; try { handler = (T)list[i]; } catch (Exception innerException) { IEventSystemHandler eventSystemHandler = list[i]; Debug.LogException(new Exception($"Type {typeof(T).Name} expected {eventSystemHandler.GetType().Name} received.", innerException)); continue; } try { functor(handler, eventData); } catch (Exception exception) { Debug.LogException(exception); } } int count2 = list.Count; s_HandlerListPool.Release(list); return count2 > 0; }
也就是说最后会执行 functor(handler, eventData);,这玩意是前面传过来的,也就是ExecuteEvents.pointerClickHandler,继续看里面
public static EventFunction<IPointerClickHandler> pointerClickHandler => s_PointerClickHandler;
是一个委托,指向了s_PointerClickHandler
private static readonly EventFunction<IPointerClickHandler> s_PointerClickHandler = Execute;
private static void Execute(IPointerClickHandler handler, BaseEventData eventData)
{
handler.OnPointerClick(ValidateEventData<PointerEventData>(eventData));
}
到此就明了了,所有的点击事件都是通过invoke这个s_PointerClickHandler委托实现的
那我们就重写覆盖这个委托,因为是private的,所以需要用到反射
using System.Reflection; using UnityEngine; using UnityEngine.EventSystems; /// <summary> /// 游戏内所有的按钮点击的共用内置CD /// </summary> public class PointerClickCoolDown : MonoBehaviour { /// <summary> /// 延迟时间 /// </summary> [SerializeField] float m_CoolDownDuration = 0.5f; bool m_EnableSelectable = true; void Awake() { typeof(ExecuteEvents).GetField("s_PointerClickHandler", BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, new ExecuteEvents.EventFunction<IPointerClickHandler>(OnPointerClick)); } void OnPointerClick(IPointerClickHandler handler, BaseEventData eventData) { PointerEventData pointerEventData = ExecuteEvents.ValidateEventData<PointerEventData>(eventData); if (pointerEventData != null) { if (!m_EnableSelectable) { return; } handler.OnPointerClick(pointerEventData); m_EnableSelectable = false; // 自己实现的一个计时器 this.StartDelayAction(m_CoolDownDuration, () => { m_EnableSelectable = true; }); } } }
到此就实现了全局拦截点击事件,自定义自己想实现的功能
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。