uGUI之EventSystem
UI的事件系统
EventSystem
uGUI系统的事件监测派发系统
在update中监测执行, List
进行事件收集、监测、分发。 实现细节
输入、监测+派发、处理
输入 InputModules, 基类 BaseInputModule,分别实现 PointerInputModule、StandaloneInputModule、TouchInputModule 事件的监测在InputModule的Process处理,使用 Raycaster监测和输入事件数据生成, 并调用统一EventSystem的 RaycastAll监测和分发。
- 处理系统
protected virtual void Update() { //虽然是列表,还是作为单例使用 if (current != this) return; //更新 InputModules 列表 //虽然是列表,还是作为单例使用 TickModules(); bool changedModule = false; for (var i = 0; i < m_SystemInputModules.Count; i++) { var module = m_SystemInputModules[i]; if (module.IsModuleSupported() && module.ShouldActivateModule()) { if (m_CurrentInputModule != module) { ChangeEventModule(module); changedModule = true; } break; } } // no event module set... set the first valid one... if (m_CurrentInputModule == null) { for (var i = 0; i < m_SystemInputModules.Count; i++) { var module = m_SystemInputModules[i]; if (module.IsModuleSupported()) { //调用pre 取消激活, model激活 ChangeEventModule(module); changedModule = true; break; } } } // 切换的需要等一帧,否则立即执行 Process if (!changedModule && m_CurrentInputModule != null) m_CurrentInputModule.Process(); }输入
BaseInputModule,继承自UIBehaviour
protected override void OnEnable() { base.OnEnable(); m_EventSystem = GetComponent<EventSystem>(); //更新EventSystem的BaseInputModule组件列表 m_EventSystem.UpdateModules(); } protected override void OnDisable() { //更新EventSystem的BaseInputModule组件列表 m_EventSystem.UpdateModules(); base.OnDisable(); }// 主要的公开功能 : public abstract void Process(); public virtual bool ShouldActivateModule() { return enabled && gameObject.activeInHierarchy; } public virtual void DeactivateModule() {} /// <summary> /// Called when the module is activated. Override this if you want custom code to execute when you activate your module. /// </summary> public virtual void ActivateModule() {} /// <summary> /// Update the internal state of the Module. /// </summary> public virtual void UpdateModule() {} /// <summary> /// Check to see if the module is supported. Override this if you have a platform specific module (eg. TouchInputModule that you do not want to activate on standalone.) /// </summary> /// <returns>Is the module supported.</returns> public virtual bool IsModuleSupported() { return true; }PointerInputModule 继承自 BaseInputModule
鼠标输入:
左键、右键、中间键、模拟触摸键//todoStandaloneInputModule
鼠标键盘
ShouldActivateModule实现
// 监测是否有输入 public override bool ShouldActivateModule() { // enabled && gameObject.activeInHierarchy; if (!base.ShouldActivateModule()) return false; var shouldActivate = m_ForceModuleActive; shouldActivate |= input.GetButtonDown(m_SubmitButton); shouldActivate |= input.GetButtonDown(m_CancelButton); shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_HorizontalAxis), 0.0f); shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_VerticalAxis), 0.0f); shouldActivate |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f; shouldActivate |= input.GetMouseButtonDown(0); if (input.touchCount > 0) shouldActivate = true; return shouldActivate; }DeactivateModule 非激活状态清除当前储存的输入
public override void DeactivateModule() { base.DeactivateModule(); ClearSelection(); }protected void ClearSelection() { var baseEventData = GetBaseEventData(); foreach (var pointer in m_PointerData.Values) { // clear all selection HandlePointerExitAndEnter(pointer, null); } m_PointerData.Clear(); eventSystem.SetSelectedGameObject(null, baseEventData); }ActivateModule
//更新位置和选择物体 public override void ActivateModule() { if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus()) return; base.ActivateModule(); m_MousePosition = input.mousePosition; m_LastMousePosition = input.mousePosition; var toSelect = eventSystem.currentSelectedGameObject; if (toSelect == null) toSelect = eventSystem.firstSelectedGameObject; eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData()); }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; }IsModuleSupported
public override bool IsModuleSupported() { //强制激活 or 鼠标设备 or 触摸设备 return m_ForceModuleActive || input.mousePresent || input.touchSupported; }Process
//Navigation 和鼠标事件 public override void Process() { if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus()) return; bool usedEvent = SendUpdateEventToSelectedObject(); // case 1004066 - touch / mouse events should be processed before navigation events in case // they change the current selected gameobject and the submit button is a touch / mouse button. // touch needs to take precedence because of the mouse emulation layer //通过BaseRaycaster进行事件监测 if (!ProcessTouchEvents() && input.mousePresent) //ExcuteEvent 回调触发 ProcessMouseEvent(); if (eventSystem.sendNavigationEvents) { if (!usedEvent) usedEvent |= SendMoveEventToSelectedObject(); if (!usedEvent) SendSubmitEventToSelectedObject(); } }
TouchInputModule
// todo 类似 StandaloneInputModule对组件进行事件回调
通过在EventSystem.ExcuteEvents中进行获取组件的
IEventSystemHandler接口, 进行分类派发不同事件- IPointerEnterHandler,
- IPointerExitHandler,
- IPointerDownHandler,
- IPointerUpHandler,
- IPointerClickHandler,
- IInitializePotentialDragHandler,
- IBeginDragHandler,
- IDragHandler,
- IEndDragHandler,
- IDropHandler,
- IScrollHandler,
- IUpdateSelectedHandler,
- ISelectHandler,
- IDeselectHandler,
- IMoveHandler,
- ISubmitHandler,
- ICancelHandler
Raycaster
辅助监测 BaseRaycaster基类
Physics2DRaycaster、PhysicsRaycaster,GraphicRaycaster, 主要实现不同的Raycast函数,区别在于 index, sortlayer, sortOrder, distance 注册到 RaycasterManager 统一调用
BaseRaycaster
protected override void OnEnable() { base.OnEnable(); RaycasterManager.AddRaycaster(this); } protected override void OnDisable() { RaycasterManager.RemoveRaycasters(this); base.OnDisable(); }//父节点 Raycaster private BaseRaycaster m_RootRaycaster;//主要的监测接口 public abstract void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList);//触发射线监测的相机 public abstract Camera eventCamera { get; }PhysicsRaycaster
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList) { Ray ray = new Ray(); float distanceToClipPlane = 0; // eventPosition = Display.RelativeMouseAt(eventData.position); //1. 检查相机和显示器的匹配 //2. 检查坐标在相机的渲染区域内 //3. 满足条件的情况下: // ray = eventCamera.ScreenPointToRay(eventPosition) // float projectionDirection = ray.direction.z; // 相对相机距离的倒数, 距离相机越近 值越大 // Mathf.Abs((eventCamera.farClipPlane - eventCamera.nearClipPlane) / projectionDirection) if (!ComputeRayAndDistance(eventData, ref ray, ref distanceToClipPlane)) return; int hitCount = 0; // 距离 mask进行raycast引擎调用,获取射线碰撞 if (m_MaxRayIntersections == 0) { if (ReflectionMethodsCache.Singleton.raycast3DAll == null) return; m_Hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, finalEventMask); hitCount = m_Hits.Length; } else { if (ReflectionMethodsCache.Singleton.getRaycastNonAlloc == null) return; if (m_LastMaxRayIntersections != m_MaxRayIntersections) { m_Hits = new RaycastHit[m_MaxRayIntersections]; m_LastMaxRayIntersections = m_MaxRayIntersections; } hitCount = ReflectionMethodsCache.Singleton.getRaycastNonAlloc(ray, m_Hits, distanceToClipPlane, finalEventMask); } //距离排序 if (hitCount > 1) System.Array.Sort(m_Hits, (r1, r2) => r1.distance.CompareTo(r2.distance)); // RaycastHit 转换成RaycastResult 添加到列表 if (hitCount != 0) { for (int b = 0, bmax = hitCount; b < bmax; ++b) { var result = new RaycastResult { gameObject = m_Hits[b].collider.gameObject, module = this, distance = m_Hits[b].distance, worldPosition = m_Hits[b].point, worldNormal = m_Hits[b].normal, screenPosition = eventData.position, index = resultAppendList.Count, sortingLayer = 0, sortingOrder = 0 }; resultAppendList.Add(result); } } }GraphicRaycaster 继承自 BaseRaycaster
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList) { //ui需要在Canvas下 if (canvas == null) return; //可渲染组件 var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas); if (canvasGraphics == null || canvasGraphics.Count == 0) return; //条件监测:1. 同相机同显示目标,2. 需要坐标在显示的范围内 int displayIndex; var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null) displayIndex = canvas.targetDisplay; else displayIndex = currentEventCamera.targetDisplay; var eventPosition = Display.RelativeMouseAt(eventData.position); if (eventPosition != Vector3.zero) { // We support multiple display and display identification based on event position. int eventDisplayIndex = (int)eventPosition.z; // Discard events that are not part of this display so the user does not interact with multiple displays at once. if (eventDisplayIndex != displayIndex) return; } else { // The multiple display system is not supported on all platforms, when it is not supported the returned position // will be all zeros so when the returned index is 0 we will default to the event data to be safe. eventPosition = eventData.position; // We dont really know in which display the event occured. We will process the event assuming it occured in our display. } // Convert to view space Vector2 pos; if (currentEventCamera == null) { // Multiple display support only when not the main display. For display 0 the reported // resolution is always the desktops resolution since its part of the display API, // so we use the standard none multiple display method. (case 741751) float w = Screen.width; float h = Screen.height; if (displayIndex > 0 && displayIndex < Display.displays.Length) { w = Display.displays[displayIndex].systemWidth; h = Display.displays[displayIndex].systemHeight; } pos = new Vector2(eventPosition.x / w, eventPosition.y / h); } else pos = currentEventCamera.ScreenToViewportPoint(eventPosition); // If it's outside the camera's viewport, do nothing if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f) return; float hitDistance = float.MaxValue; Ray ray = new Ray(); //射线监测,获取第一个碰撞的信息 if (currentEventCamera != null) ray = currentEventCamera.ScreenPointToRay(eventPosition); if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None) { float distanceToClipPlane = 100.0f; if (currentEventCamera != null) { float projectionDirection = ray.direction.z; distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection) ? Mathf.Infinity : Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection); } if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All) { if (ReflectionMethodsCache.Singleton.raycast3D != null) { var hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, (int)m_BlockingMask); if (hits.Length > 0) hitDistance = hits[0].distance; } } if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All) { if (ReflectionMethodsCache.Singleton.raycast2D != null) { var hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, (int)m_BlockingMask); if (hits.Length > 0) hitDistance = hits[0].distance; } } } m_RaycastResults.Clear(); //从符合条件的可渲染组件进行位置监测 Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults); int totalCount = m_RaycastResults.Count; for (var index = 0; index < totalCount; index++) { var go = m_RaycastResults[index].gameObject; bool appendGraphic = true; if (ignoreReversedGraphics) { //监测UI是否垂直于相机 if (currentEventCamera == null) { // If we dont have a camera we know that we should always be facing forward var dir = go.transform.rotation * Vector3.forward; appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0; } else { // If we have a camera compare the direction against the cameras forward. var cameraFoward = currentEventCamera.transform.rotation * Vector3.forward; var dir = go.transform.rotation * Vector3.forward; appendGraphic = Vector3.Dot(cameraFoward, dir) > 0; } } if (appendGraphic) { float distance = 0; Transform trans = go.transform; Vector3 transForward = trans.forward; if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay) distance = 0; else { // http://geomalgorithms.com/a06-_intersect-2.html distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction)); // Check to see if the go is behind the camera. if (distance < 0) continue; } if (distance >= hitDistance) continue; var castResult = new RaycastResult { gameObject = go, module = this, distance = distance, screenPosition = eventPosition, 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); } } }//RectTransform的Raycast监测方式 private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results) { // Necessary for the event system int totalCount = foundGraphics.Count; for (int i = 0; i < totalCount; ++i) { Graphic graphic = foundGraphics[i]; // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn if (graphic.depth == -1 || !graphic.raycastTarget || graphic.canvasRenderer.cull) continue; if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera)) continue; // z 不能超过远裁剪面 if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane) continue; if (graphic.Raycast(pointerPosition, eventCamera)) { s_SortedGraphics.Add(graphic); } } s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth)); totalCount = s_SortedGraphics.Count; for (int i = 0; i < totalCount; ++i) results.Add(s_SortedGraphics[i]); s_SortedGraphics.Clear(); }Physics2DRaycaster 继承自 PhysicsRaycaster
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList) { Ray ray = new Ray(); float distanceToClipPlane = 0; if (!ComputeRayAndDistance(eventData, ref ray, ref distanceToClipPlane)) return; int hitCount = 0; if (maxRayIntersections == 0) { if (ReflectionMethodsCache.Singleton.getRayIntersectionAll == null) return; m_Hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, finalEventMask); hitCount = m_Hits.Length; } else { if (ReflectionMethodsCache.Singleton.getRayIntersectionAllNonAlloc == null) return; if (m_LastMaxRayIntersections != m_MaxRayIntersections) { m_Hits = new RaycastHit2D[maxRayIntersections]; m_LastMaxRayIntersections = m_MaxRayIntersections; } hitCount = ReflectionMethodsCache.Singleton.getRayIntersectionAllNonAlloc(ray, m_Hits, distanceToClipPlane, finalEventMask); } if (hitCount != 0) { for (int b = 0, bmax = hitCount; b < bmax; ++b) { var sr = m_Hits[b].collider.gameObject.GetComponent<SpriteRenderer>(); var result = new RaycastResult { gameObject = m_Hits[b].collider.gameObject, module = this, distance = Vector3.Distance(eventCamera.transform.position, m_Hits[b].point), worldPosition = m_Hits[b].point, worldNormal = m_Hits[b].normal, screenPosition = eventData.position, index = resultAppendList.Count, sortingLayer = sr != null ? sr.sortingLayerID : 0, sortingOrder = sr != null ? sr.sortingOrder : 0 }; resultAppendList.Add(result); } } }事件排序规则
private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs) { //不同的 BaseRaycaster if (lhs.module != rhs.module) { var lhsEventCamera = lhs.module.eventCamera; var rhsEventCamera = rhs.module.eventCamera; //1. Raycaster的不相机的depth 越小越靠前 if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth) { // need to reverse the standard compareTo if (lhsEventCamera.depth < rhsEventCamera.depth) return 1; if (lhsEventCamera.depth == rhsEventCamera.depth) return 0; return -1; } //2. Raycaster的排序优先级 if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority) return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority); //3. Raycaster的渲染优先级 if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority) return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority); } //层级排序 if (lhs.sortingLayer != rhs.sortingLayer) { // Uses the layer value to properly compare the relative order of the layers. var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer); var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer); return rid.CompareTo(lid); } //深度值排序 if (lhs.sortingOrder != rhs.sortingOrder) return rhs.sortingOrder.CompareTo(lhs.sortingOrder); // comparing depth only makes sense if the two raycast results have the same root canvas (case 912396) if (lhs.depth != rhs.depth && lhs.module.rootRaycaster == rhs.module.rootRaycaster) return rhs.depth.CompareTo(lhs.depth); //距离 if (lhs.distance != rhs.distance) return lhs.distance.CompareTo(rhs.distance); //条件相同的情况 使用射线监测的反向顺序 return lhs.index.CompareTo(rhs.index); }
其他
- Display.RelativeMouseAt 对于多显示器取相对位置。 z轴为相机的
targetDisplay - RectTransformUtility.RectangleContainsScreenPoint 监测屏幕点包含于Rect
- Display.RelativeMouseAt 对于多显示器取相对位置。 z轴为相机的