场景结构
测试代码
public class TestBtn : MonoBehaviour
{
void Start()
{
var btn = GetComponent<Button>();
btn.onClick.AddListener(OnClick);
}
private void OnClick()
{
Debug.Log("666");
}
}
当添加事件时
// 实例化一个ButtonClickedEvent的事件
[FormerlySerializedAs("onClick")]
[SerializeField]
private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();
//常用的onClick.AddListener()就是监听这个事件
public ButtonClickedEvent onClick
{
get { return m_OnClick; }
set { m_OnClick = value; }
}
//Button.cs部分源码
当按钮点击时
//如果按钮处于活跃状态并且可交互(Interactable设置为true),则触发事件
private void Press()
{
if (!IsActive() || !IsInteractable())
return;
UISystemProfilerApi.AddMarker("Button.onClick", this);
m_OnClick.Invoke();
}
//鼠标点击时调用该函数,继承自 IPointerClickHandler 接口
public virtual void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
Press();
}
//Button.cs部分源码
是如何执行的呢
private static readonly EventFunction<IPointerClickHandler> s_PointerClickHandler = Execute;
public static EventFunction<IPointerClickHandler> pointerClickHandler
{
get { return s_PointerClickHandler; }
}
//调用关键代码
private static void Execute(IPointerClickHandler handler, BaseEventData eventData)
{
handler.OnPointerClick(ValidateEventData<PointerEventData>(eventData));
}
//ExecuteEvents.cs部分源码
- 实际调用了目标对象(如 Button)的
OnPointerClick()
方法。
继续跟踪源码在StandaloneInputModule中
private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo)
{
// 执行 PointerUp 事件处理
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
// 获取可以处理点击事件的对象
var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// 处理 PointerClick 和 Drop 事件
if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
{
// 如果当前对象可以接收点击事件并且符合点击条件,则执行 PointerClick 事件
ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
}
//....省略其他代码...
}
//StandaloneInputModule.cs部分源码
关键点:
pointerEvent.eligibleForClick
表示这次点击是否满足触发条件(比如没有被拖动打断)。- 获取当前鼠标释放时的 GameObject (
currentOverGo
)。 - 获取其
IPointerClickHandler
接口实现者(即 Button 组件)。 - 最后通过
ExecuteEvents.Execute(...)
调用OnPointerClick
方法。
pointerEvent.eligibleForClick在下面会设置为true
而ReleaseMouse在UpdateModule中调用
public override void UpdateModule()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
{
if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
{
//关键代码,处理鼠标释放事件
ReleaseMouse(m_InputPointerEvent, m_InputPointerEvent.pointerCurrentRaycast.gameObject);
}
m_InputPointerEvent = null;
return;
}
m_LastMousePosition = m_MousePosition;
m_MousePosition = input.mousePosition;
}
//StandaloneInputModule.cs部分源码
- 主要用于记录鼠标位置。
- 如果窗口失焦并且正在拖拽,则调用
ReleaseMouse()
来释放鼠标状态。
而UpdateModule是BaseInputModule 类的方法
public abstract class BaseInputModule : UIBehaviour{
public virtual void UpdateModule(){}
}
UpdateModule被在EventSystem中的TickModules方法调用
private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();
private void TickModules()
{
var systemInputModulesCount = m_SystemInputModules.Count;
for (var i = 0; i < systemInputModulesCount; i++)
{
if (m_SystemInputModules[i] != null)
m_SystemInputModules[i].UpdateModule();
}
}
//EventSystem.cs部分源码
TickModules方法被在EventSystem里的Update调用
protected virtual void Update()
{
if (current != this)
return;
TickModules();
bool changedModule = false;
//m_CurrentInputModule 就是场景里面的StandaloneInputModule组件
//判断当前m_CurrentInputModule 有没有被更改或者为空的情况
if (!changedModule && m_CurrentInputModule != null)
//处理鼠标事件
m_CurrentInputModule.Process();
//...省略其他代码...
}
EventSystem的Update继承UIBehaviour
public class EventSystem : UIBehaviour
EventSystem
是一个继承自UIBehaviour
的组件,必须挂载在场景中的某个 GameObject 上。- 它每帧调用自身的
Update()
方法(由 Unity 引擎自动调用):
所以,如果在按下鼠标后,EventSystem每帧都会检测何时释放鼠标,然后触发点击事件。如果有的话触发点击事件,那么是如何知道要触发的哪个Button的点击事件的呢
回到EventSystem的Update方法中
protected virtual void Update()
{
if (current != this)
return;
TickModules();
bool changedModule = false;
//m_CurrentInputModule 就是场景里面的StandaloneInputModule组件
//判断当前m_CurrentInputModule 有没有被更改或者为空的情况
if (!changedModule && m_CurrentInputModule != null)
//处理鼠标事件
m_CurrentInputModule.Process();
//...省略其他代码...
}
- 这里会调用所有注册的
BaseInputModule
子类的UpdateModule()
方法。 - 其中就包括
StandaloneInputModule
。
可以看到,如果没有变更输入模块并且当前输入模块不为空,会在每帧执行Process方法
public abstract class BaseInputModule : UIBehaviour
{
public abstract void Process();
}
而场景里面的StandaloneInputModule 继承了PointerInputModule
public class StandaloneInputModule : PointerInputModule
PointerInputModule实现了BaseInputModule接口
public abstract class PointerInputModule : BaseInputModule
StandaloneInputModule 重写了Process方法
public override void Process()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
return;
bool usedEvent = SendUpdateEventToSelectedObject();
// 案例 1004066 - 在处理导航事件之前应先处理触摸/鼠标事件,
// 因为它们可能会改变当前选中的游戏对象,并且提交按钮可能是触摸/鼠标按钮。
// 由于存在鼠标模拟层,触摸需要优先处理。
if (!ProcessTouchEvents() && input.mousePresent)
//处理鼠标事件
ProcessMouseEvent();
if (eventSystem.sendNavigationEvents)
{
if (!usedEvent)
usedEvent |= SendMoveEventToSelectedObject();
if (!usedEvent)
SendSubmitEventToSelectedObject();
}
}
protected void ProcessMouseEvent()
{
//鼠标左键事件Id为0
ProcessMouseEvent(0);
}
protected void ProcessMouseEvent(int id)
{
//关键函数
var mouseData = GetMousePointerEventData(id);
var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData;
m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;
// 处理鼠标左键点击
ProcessMousePress(leftButtonData);
ProcessMove(leftButtonData.buttonData);
ProcessDrag(leftButtonData.buttonData);
// 处理鼠标右键键点击
ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData);
ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData);
ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData);
ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData);
if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f))
{
var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(leftButtonData.buttonData.pointerCurrentRaycast.gameObject);
ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler);
}
}
GetMousePointerEventData方法是StandaloneInputModule 继承PointerInputModule的
protected virtual MouseState GetMousePointerEventData(int id)
{
// Populate the left button...
PointerEventData leftData;
var created = GetPointerData(kMouseLeftId, out leftData, true);
leftData.Reset();
if (created)
leftData.position = input.mousePosition;
Vector2 pos = input.mousePosition;
if (Cursor.lockState == CursorLockMode.Locked)
{
// We don't want to do ANY cursor-based interaction when the mouse is locked
leftData.position = new Vector2(-1.0f, -1.0f);
leftData.delta = Vector2.zero;
}
else
{
leftData.delta = pos - leftData.position;
leftData.position = pos;
}
leftData.scrollDelta = input.mouseScrollDelta;
leftData.button = PointerEventData.InputButton.Left;
//发射射线
eventSystem.RaycastAll(leftData, m_RaycastResultCache);
//省略后面的代码----下面会分析
}
上面代码主要作用是发射射线,填充MouseButtonEventData 事件
EventSystem的RaycastAll方法
public class EventSystem : UIBehaviour
{
public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
{
raycastResults.Clear();
var modules = RaycasterManager.GetRaycasters();
var modulesCount = modules.Count;
for (int i = 0; i < modulesCount; ++i)
{
var module = modules[i];
if (module == null || !module.IsActive())
continue;
//发射射线
module.Raycast(eventData, raycastResults);
}
raycastResults.Sort(s_RaycastComparer);
}
}
这个RaycasterManager会收集场景里面所有实现BaseRaycaster接口的类
由于我们场景里面只有Canvas上面挂载了GraphicRaycaster 组件
//这部分详细解析可以查看这篇文章
public class GraphicRaycaster : BaseRaycaster
{
private List<Graphic> m_RaycastResults = new List<Graphic>();
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList) {
//。。。省略部分代码。。。
//拿到所有继承了Graphic的类,Button继承了该类
Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
//。。。省略部分代码。。。
int totalCount = m_RaycastResults.Count;
for (var index = 0; index < totalCount; index++){
//。。。省略部分代码。。。
//m_RaycastResults进行结果排序
//构建结果返回
var castResult = new RaycastResult
{
gameObject = go,//这个就是我们的Button对象或者Text对象
module = this,
distance = distance,
screenPosition = eventPosition,
displayIndex = displayIndex,
index = resultAppendList.Count,
depth = m_RaycastResults[index].depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
worldPosition = ray.origin + ray.direction * distance,
worldNormal = -transForward
};
resultAppendList.Add(castResult);
}
}
可以看到拿到的结果有两个
继续查看PointerInputModule的GetMousePointerEventData方法
protected virtual MouseState GetMousePointerEventData(int id)
{
//省略前面的代码,之前分析过了
//发射射线
eventSystem.RaycastAll(leftData, m_RaycastResultCache);
//拿到第一个结果,即Text所在对象
var raycast = FindFirstRaycast(m_RaycastResultCache);
//赋值给leftData.pointerCurrentRaycast,后面需要用到
leftData.pointerCurrentRaycast = raycast;
m_RaycastResultCache.Clear();
// copy the apropriate data into right and middle slots
PointerEventData rightData;
GetPointerData(kMouseRightId, out rightData, true);
rightData.Reset();
CopyFromTo(leftData, rightData);
rightData.button = PointerEventData.InputButton.Right;
PointerEventData middleData;
GetPointerData(kMouseMiddleId, out middleData, true);
middleData.Reset();
CopyFromTo(leftData, middleData);
middleData.button = PointerEventData.InputButton.Middle;
m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);
return m_MouseState;
}
处理点击事件函数
/// <summary>
/// 计算并处理任何鼠标按钮状态的变化。
/// </summary>
protected void ProcessMousePress(MouseButtonEventData data)
{
// 获取当前的指针事件数据
var pointerEvent = data.buttonData;
// 这个对象就是前面拿到的Text所在的对象
var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
// 处理按下事件
if (data.PressedThisFrame())
{
// 标记该事件为可点击
pointerEvent.eligibleForClick = true; //关键代码,前面释放鼠标需要用到
// 重置增量位置
pointerEvent.delta = Vector2.zero;
// 重置拖动标志
pointerEvent.dragging = false;
// 使用拖拽阈值
pointerEvent.useDragThreshold = true;
// 设置按下位置
pointerEvent.pressPosition = pointerEvent.position;
// 设置按下时的射线检测结果
pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
// 如果选择的对象发生了变化,则取消之前的选中状态
DeselectIfSelectionChanged(currentOverGo, pointerEvent);
// 查找继承了IPointerDownHandler的控件,因为Text没有实现改接口,所以向上查找可以处理点击事件的对象,找到了Button对象
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// 如果没有找到按下处理器,则查找点击处理器
if (newPressed == null)
newPressed = newClick;
// Debug.Log("Pressed: " + newPressed);
float time = Time.unscaledTime;
// 如果新的按下对象与上次相同,则增加点击计数
if (newPressed == pointerEvent.lastPress)
{
var diffTime = time - pointerEvent.clickTime;
if (diffTime < 0.3f)
++pointerEvent.clickCount;
else
pointerEvent.clickCount = 1;
pointerEvent.clickTime = time;
}
else
{
pointerEvent.clickCount = 1;
}
// 设置按下对象、原始按下对象和点击对象
pointerEvent.pointerPress = newPressed;
pointerEvent.rawPointerPress = currentOverGo;
pointerEvent.pointerClick = newClick;
pointerEvent.clickTime = time;
// 保存拖拽处理器
pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
// 如果有拖拽处理器,执行初始化潜在拖拽操作
if (pointerEvent.pointerDrag != null)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
// 保存输入指针事件
m_InputPointerEvent = pointerEvent;
}
// 处理释放事件
if (data.ReleasedThisFrame())
{
ReleaseMouse(pointerEvent, currentOverGo);
}
}
这里面 pointerEvent.eligibleForClick = true;
前面释放鼠标需要用到。
ExecuteEvents最终执行按下事件
public static class ExecuteEvents{
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;
}
}
public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) where T : IEventSystemHandler
{
GetEventChain(root, s_InternalTransformList);
var internalTransformListCount = s_InternalTransformList.Count;
for (var i = 0; i < internalTransformListCount; i++)
{
var transform = s_InternalTransformList[i];
//关键函数,执行点击事件
if (Execute(transform.gameObject, eventData, callbackFunction))
return transform.gameObject;
}
return null;
}
public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
{
var internalHandlers = ListPool<IEventSystemHandler>.Get();
GetEventList<T>(target, internalHandlers);
// if (s_InternalHandlers.Count > 0)
// Debug.Log("Executinng " + typeof (T) + " on " + target);
var internalHandlersCount = internalHandlers.Count;
for (var i = 0; i < internalHandlersCount; 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;
ListPool<IEventSystemHandler>.Release(internalHandlers);
return handlerCount > 0;
}
//获取实现了IPointerDownHandler接口的组件
private static void GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
{
// Debug.LogWarning("GetEventList<" + typeof(T).Name + ">");
if (results == null)
throw new ArgumentException("Results array is null", "results");
if (go == null || !go.activeInHierarchy)
return;
var components = ListPool<Component>.Get();
go.GetComponents(components);
var componentsCount = components.Count;
for (var i = 0; i < componentsCount; i++)
{
if (!ShouldSendToComponent<T>(components[i]))
continue;
// Debug.Log(string.Format("{2} found! On {0}.{1}", go, s_GetComponentsScratch[i].GetType(), typeof(T)));
results.Add(components[i] as IEventSystemHandler);
}
ListPool<Component>.Release(components);
// Debug.LogWarning("end GetEventList<" + typeof(T).Name + ">");
}
}
GetEventChain拿到4个结果
分别是
最终触发IPointerDownHandler事件
总结如下
EventSystem
会在每帧检测鼠标左键是否按下,如果按下,发射射线,拿到第一个检查到的物体,执行IPointerDownHandler
事件,同时构造好PointerEventData
给后面释放鼠标时使用,在鼠标释放时,查找点击的GameObject
上是否有组件实现了IPointerClickHandler
接口,如果有就触发事件,如果没有,就向父节点GameObject
查找,直到找到发出射线的Canvas
为止。
✅ 总结:整个流程图解
阶段 | 内容 |
---|---|
📌 Unity 引擎调用 | EventSystem.Update() |
🔁 每帧更新 | TickModules() → StandaloneInputModule.UpdateModule() |
🖱️ 鼠标释放 | ReleaseMouse() 被调用 |
🎯 查找点击对象 | 使用 GetEventHandler<IPointerClickHandler>() |
⚡ 执行点击 | ExecuteEvents.Execute() → handler.OnPointerClick() |
🧱 Button 响应 | OnPointerClick() → Press() → m_OnClick.Invoke() |
📈 用户监听 | onClick.AddListener(() => { ... }) 中的方法被调用 |