uGUI之EventSystem

UI的事件系统

  1. EventSystem

    uGUI系统的事件监测派发系统

    在update中监测执行, List 进行事件收集、监测、分发。

    1. 实现细节

      输入、监测+派发、处理

      输入 InputModules, 基类 BaseInputModule,分别实现 PointerInputModule、StandaloneInputModule、TouchInputModule 事件的监测在InputModule的Process处理,使用 Raycaster监测和输入事件数据生成, 并调用统一EventSystem的 RaycastAll监测和分发。

      1. 处理系统
      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();
              }
      1. 输入

        1. 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;
          }
        2. PointerInputModule 继承自 BaseInputModule

          鼠标输入:左键右键中间键模拟触摸键

          //todo
        3. StandaloneInputModule

          鼠标键盘

          1. 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;
            }
          2. 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);
                    }
          3. 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());
                    }
          4. 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;
                  }
          1. IsModuleSupported

               public override bool IsModuleSupported()
                    {
                         //强制激活 or 鼠标设备 or 触摸设备
                        return m_ForceModuleActive || input.mousePresent || input.touchSupported;
                    }
          2. 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();
                        }
                    }
        4. TouchInputModule

          // todo 类似 StandaloneInputModule
        5. 对组件进行事件回调

          通过在EventSystem.ExcuteEvents中进行获取组件的 IEventSystemHandler接口, 进行分类派发不同事件

          1. IPointerEnterHandler,
          2. IPointerExitHandler,
          3. IPointerDownHandler,
          4. IPointerUpHandler,
          5. IPointerClickHandler,
          6. IInitializePotentialDragHandler,
          7. IBeginDragHandler,
          8. IDragHandler,
          9. IEndDragHandler,
          10. IDropHandler,
          11. IScrollHandler,
          12. IUpdateSelectedHandler,
          13. ISelectHandler,
          14. IDeselectHandler,
          15. IMoveHandler,
          16. ISubmitHandler,
          17. ICancelHandler
      2. Raycaster

        辅助监测 BaseRaycaster基类

        Physics2DRaycaster、PhysicsRaycaster,GraphicRaycaster, 主要实现不同的Raycast函数,区别在于 index, sortlayer, sortOrder, distance 注册到 RaycasterManager 统一调用

        1. 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; }
        2. 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);
                          }
                      }
                  }
        3. 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();
                  }
        4. 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);
                        }
                    }
                }
        1. 事件排序规则

           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);
                  }
      3. 其他

        1. Display.RelativeMouseAt 对于多显示器取相对位置。 z轴为相机的 targetDisplay
        2. RectTransformUtility.RectangleContainsScreenPoint 监测屏幕点包含于Rect


文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录