- Unity3D高级编程:主程手记
- 陆泽西
- 679字
- 2022-01-07 14:46:27
4.3.4 输入事件捕获模块源码
输入事件捕获模块由BaseInputModule、PointerInputModule、StandaloneInputModule、TouchInputModule四个类组成。
BaseInputModule类是抽象(abstract)基类,提供必需的空接口和基本变量。
PointerInputModule类继承自BaseInputModule类,并且在其基础上扩展了关于点位的输入逻辑,增加了输入的类型和状态。
StandaloneInputModule类和TouchInputModule类又继承自PointerInputModule类,它们从父类开始向不同的方向拓展。
StandaloneInputModule类向标准键盘鼠标输入方向拓展,而TouchInputModule类向触控板输入方向拓展。
它们的核心部分代码如下:
///<summary> /// 处理所有的鼠标事件 ///</summary> protected void ProcessMouseEvent(int id) { var mouseData = GetMousePointerEventData(id); // 通过id获取鼠标事件数据 // 再通过鼠标数据获取鼠标左键的事件数据 var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton. Left).eventData; // 处理鼠标左键相关的事件 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>(left ButtonData.buttonData.pointerCurrentRaycast.gameObject); ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler); } }
以上代码为StandaloneInputModule类的主函数ProcessMouseEvent()的代码,它从鼠标键盘输入事件上扩展了输入的逻辑,处理鼠标的按下、移动、滚轮、拖曳等操作事件。其中比较重要的函数为ProcessMousePress()、ProcessMove()、ProcessDrag()这三个函数,我们来重点看看它们处理的内容,其源码如下:
///<summary> /// 处理鼠标按下事件 ///</summary> protected void ProcessMousePress(MouseButtonEventData data) { var pointerEvent = data.buttonData; 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); // 搜索元件中按下事件的句柄,并执行按下事件句柄 var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler); // 搜索后找不到句柄,就设置一个自己的句柄 if (newPressed == null) newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler> (currentOverGo); // 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.clickTime = time; // 保存拖曳信息 pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler> (currentOverGo); // 执行拖曳启动事件句柄 if (pointerEvent.pointerDrag != null) ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag); } // 鼠标或手指松开事件 if (data.ReleasedThisFrame()) { // 执行鼠标或手指松开事件的句柄 // Debug.Log("Executing pressup on: " + pointer.pointerPress); ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents. pointerUpHandler); // Debug.Log("KeyCode: " + pointer.eventData.keyCode); var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClick Handler>(currentOverGo); // 如果鼠标或手指松开时与按下时为同一个元素,那就是点击 if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent. eligibleForClick) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler); } // 否则也可能是拖曳的释放 else if (pointerEvent.pointerDrag != null && pointerEvent.dragging) { ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler); } pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = 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); } } }
上面展示了ProcessMousePress()函数处理鼠标按下事件的代码,虽然比较多但并不复杂,我在代码上做了详细注解。该函数不仅处理了鼠标按下的操作,还处理了鼠标松开时的操作,以及拖曳启动和拖曳松开与结束的事件。在调用处理相关句柄的前后,事件数据都会保存在pointerEvent类中,然后被传递给业务层中设置的输入事件句柄。
我们再来看看ProcessDrag()拖曳处理函数,其源码如下:
protected virtual void ProcessDrag(PointerEventData pointerEvent) { bool moving = pointerEvent.IsPointerMoving(); // 如果已经在移动,且还没开始拖曳启动事件,则调用拖曳启动句柄,并设置拖曳中标记为true if (moving && pointerEvent.pointerDrag != null && !pointerEvent.dragging && ShouldStartDrag(pointerEvent.pressPosition, pointerEvent.position, eventSystem.pixelDragThreshold, pointerEvent.useDragThreshold)) { ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents. beginDragHandler); pointerEvent.dragging = true; } // 拖曳时的句柄处理 if (pointerEvent.dragging && moving && pointerEvent.pointerDrag != null) { // 如果按下的物体和拖曳的物体不是同一个,则视为松开了拖曳,并清除前面按下时的标记 if (pointerEvent.pointerPress != pointerEvent.pointerDrag) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = null; } // 执行拖曳中的句柄 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents. dragHandler); } }
上述代码展示了ProcessDrag()拖曳句柄处理函数,与ProcessMousePress()类似,对拖曳事件逻辑进行了判断,包括拖曳开始事件处理、判断结束拖曳事件及拖曳句柄的调用。
ProcessMove()则相对比较简单,每帧都会直接调用处理句柄,其源码如下:
protected virtual void ProcessMove(PointerEventData pointerEvent) { var targetGO = pointerEvent.pointerCurrentRaycast.gameObject; HandlePointerExitAndEnter(pointerEvent, targetGO); }
除了鼠标事件外,还有触屏事件的处理方式,即TouchInputModule()的核心函数,源码如下:
///<summary> /// Process all touch events. /// 处理所有触屏事件 ///</summary> private void ProcessTouchEvents() { for (int i = 0; i<Input.touchCount; ++i) { Touch input = Input.GetTouch(i); bool released; bool pressed; var pointer = GetTouchPointerEventData(input, out pressed, out released); ProcessTouchPress(pointer, pressed, released); if (!released) { ProcessMove(pointer); ProcessDrag(pointer); } else RemovePointerData(pointer); } }
从以上代码中可以看到,ProcessMove()和ProcessDrag()与前面的鼠标事件处理是一样的,只是按下的事件处理不同,而且它对每个触点都执行了相同的操作。其实Process-TouchPress()和鼠标按下处理函数ProcessMousePress()相似,可以说基本上一模一样,只是传入时的数据类型不同而已。由于篇幅有限,这里不再重复展示长串代码。
这里大量用到了ExecuteEvents.ExecuteHierarchy()、ExecuteEvents.Execute()之类的静态函数来执行句柄,它们是怎么工作的呢?其实很简单,源代码如下:
private static readonly List<Transform>s_InternalTransformList = new List<Transform>(30); public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T>callbackFunction) where T : IEventSystemHandler { // 获取物体的所有父节点,包括它自己 GetEventChain(root, s_InternalTransformList); for (var i = 0; i<s_InternalTransformList.Count; i++) { var transform = s_InternalTransformList[i]; // 对每个父节点包括自己依次执行句柄响应 if (Execute(transform.gameObject, eventData, callbackFunction)) return transform.gameObject; } return null; }
上述代码对所有父节点都调用句柄函数。也就是说,当前节点的事件会通知给其上面的父节点。
至此我们基本清楚事件处理的基本逻辑了,下面来看看碰撞测试模块是如何运作的。