起因
策划提了个需求,具体为给游戏内的所有按钮都加一个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;
});
}
}
}
到此就实现了全局拦截点击事件,自定义自己想实现的功能