每次创建画布Canvas时会有一个名为EventSystem的游戏对象创建出来。并且挂载着Event Sysyem组件和Standalone InputModule组件

alt

Event System(事件系统管理器)

该子系统负责控制构成事件的所有其他元素。该系统会协调哪个输入模块当前处于激活状态,哪个游戏对象当前被视为“已选中”,以及许多其他高级事件系统概念。

每次“更新”时,事件系统都会收到调用、查看其输入模块并确定应该将哪个输入模块用于此活动。然后,系统会将处理委托给模块。

负责处理Unity场景中的事件。一个场景应该只包含一个EventSystem。EventSystem与许多模块一起工作,主要是保存状态和将功能委托给特定的、可重写的组件。

继承关系

EventSystem继承自UIBehaviour;UIBehaviour继承自MonoBehaviour

主要作用

  • 将对象设置为选定。将发送一个ondesselection的旧选择对象,并选择新选择的对象。如果选择对象更改执行 ExecuteEvents.deselectHandler事件和ExecuteEvents.selectHandler (相关成员:方法:SetSelectedGameObject() 属性:缓存当前选中的游戏对象currentSelectedGameObject) alt

  • 管理游戏中的InputModule(基类:BaseInputModule) alt

  • 驱动InputModule的UpdateModule和Process(具体实现逻辑在TouchInputModule, StandaloneInputModule两个类中);每帧检测 alt

  • 做射线检测(RaycastAll)和射线结果的层级比较(RaycastComparer) alt PointerInputModule类中的GetTouchPointerEventData方法调用了RaycastAll方法并返回一个PointerEventData类型的对象(事件数据)

成员

  • IsPointerOverGameObject() : 指针是否在一个事件系统对象上。alt

属性

属性 功能
First Seleced 首先选择的游戏对象
Send Navigation Events 是否允许导航事件(移动/提交/取消)
Drag Threshold 拖拽操作的容限区域(一像素为单位)

Standalone Input Module (独立输入模块)

根据设计,该模块与控制器/鼠标输入具有相同的功能。响应输入时会发送按钮按压、拖拽以及类似事件。

当鼠标/输入设备移动时,该模块将指针事件发送到组件,并使用图形射线投射器 (Graphics Raycaster)和物理射线投射器 (Physics Raycaster) 来计算给定指针设备当前指向的元素。可以配置这些射线投射器来检测或忽略场景的某些部分,从而满足的要求。

该模块会发送 Move 事件和 Submit/Cancel 事件来响应通过 Input 窗口跟踪的输入。对于键盘和控制器输入均是如此。可在模块的检视面板中配置跟踪的轴和键。

继承关系

alt

PointerInputModule继承自BaseInputModule; TouchInputModule, StandaloneInputModule继承自PointerInputModule;只StandaloneInputModule类中的方法

主要作用

  • 处理玩家输入(鼠标和触摸); 通过EventSystem中的Update触发输入模块中的Process方法-->ProcessMouseEvent方法。 alt

  • 处理鼠标事件(ProcessMouseEvent):设置获取鼠标状态并封装事件数据PointerEventData(存储在MouseState):GetMousePointerEventData(int id);返回鼠标状态 :PointerInputModule.GetButtonState(PointerEventData.InputButton button):执行部分事件;通过ExecuteEvents.Execute()/ExecuteEvents.ExecuteHierarchy()来触发对应的事件。 alt alt 在ProcessMousePress方法中会根据鼠标在当前帧的按下状态(PointerEventData.FramePressState)来判定是否要释放鼠标ReleaseMouse() alt

  • 拖拽判定: alt

属性

属性 功能
Horizontal Axis 为水平轴按钮输入所需的管理器名称。
Vertical Axis 为垂直轴输入所需的管理器名称。
Submit Button 为 Submit 按钮输入所需的管理器名称。
Cancel Button 为 Cancel 按钮输入所需的管理器名称。
Input Actions Per Second 每秒允许的键盘/控制器输入数量。
Repeat Delay 每秒输入操作重复率生效前的延迟秒数。
Force Module Active 启用此属性可强制该__独立输入模块 (Standalone Input Module)__ 处于活动状态。

详细信息

该模块:

  • 使用垂直/水平轴进行键盘和控制器导航
  • 使用 Submit/Cancel 按钮发送提交和取消事件
  • 在事件之间有一个超时值仅允许每秒的最大事件数。

该模块的流程如下:

  • 如果输入了 Input 窗口中的有效轴,则向所选对象发送 Move 事件
  • 如果按下了 Submit 或 Cancel 按钮,则向所选对象发送 Submit 或 Cancel 事件
  • 处理鼠标输入
    • 如果这是新的按压操作
      • 发送 PointerEnter 事件(向上发送到层级视图中可对其进行处理的每个对象)
      • 发送 PointerPress 事件
      • 缓存拖动处理程序(层级视图中可对其进行处理的第一个元素)
      • 将 BeginDrag 事件发送到拖动处理程序
      • 在事件系统中将“Pressed”对象设置为 Selected
    • 如果这是持续按压操作
      • 处理移动
      • 将 DragEvent 发送到缓存的拖动处理程序
      • 如果触摸在对象之间移动,则处理 PointerEnter 和 PointerExit 事件
    • 如果这是释放操作
      • 将 PointerUp 事件发送到收到 PointerPress 的对象
      • 如果当前悬停对象与 PointerPress 对象相同,则发送 PointerClick 事件
      • 如果缓存了拖动处理程序,则发送 Drop 事件
      • 将 EndDrag 事件发送到缓存的拖动处理程序
  • 处理滚轮事件

事件数据 EventDate

alt

封装的鼠标数据:

  • 当前光标指向的是哪个物体 :pointerPress
  • 是否正在拖拽 :dragging
  • 点击次数(双击的时候是2):clickCount
  • 按下时坐标 :pressPosition
  • 当前坐标 :position
  • 接收某事件的游戏对象 : PointerEnter/ pointerClick
  • 与当前事件关联的RaycastResult :pointerCurrentRaycast
  • 自上次更新以来的滚动量 :scrollDelta

事件执行 ExecuteEvent

将EventData传递给UI组件并执行对应的事件。

泛型委托:

public delegate void EventFunction<T1>(T1 handler, BaseEventData eventData);
//事件处理者,事件数据

泛型T1 : IEventSystemHandler :

alt

方法:

public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) where T : IEventSystemHandler

Execute是目标控件身上所有实现了泛型T类型组件都 执行泛型T的接口函数;

源码:

 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;
        }

GetEventList方法:

//获取指定对象的事件
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 + ">");
        }

ExecuteHierarchy则是按着层级面板向上递归查询,直到找到一个实现T接口的组件后执行泛型T的接口函数(例如IScrollHandler,IPointerDownHandler,IPointerExitHandler,IDropHandler :( A、B对象必须均实现IDropHandler接口,且A至少实现IDragHandler接口当鼠标从A对象上开始拖拽,在B对象上抬起时 B对象响应此事件) )

源码:

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;
        }

GetEventChain方法:

//以当前游戏对象为基础向上查找全部父对象
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;
            }
        }

以鼠标进入的事件为例事件执行的过程: alt

将UI的点击事件渗透下去

原文链接

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System.Collections.Generic;
 
public class Test : MonoBehaviour,IPointerClickHandler ,IPointerDownHandler,IPointerUpHandler
{ 
 
    //监听按下
    public void OnPointerDown(PointerEventData eventData)
    {
        PassEvent(eventData,ExecuteEvents.pointerDownHandler);
    }
 
    //监听抬起
    public void OnPointerUp(PointerEventData eventData)
    {
        PassEvent(eventData,ExecuteEvents.pointerUpHandler);
    }
 
    //监听点击
    public void OnPointerClick(PointerEventData eventData)
    {
        PassEvent(eventData,ExecuteEvents.submitHandler);
        PassEvent(eventData,ExecuteEvents.pointerClickHandler);
    }
 
 
    //把事件透下去;获取射线找到的全部游戏对象逐一执行指定委托
    public void  PassEvent<T>(PointerEventData data,ExecuteEvents.EventFunction<T> function)
        where T : IEventSystemHandler
    {
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(data, results); 
        GameObject current = data.pointerCurrentRaycast.gameObject ;
        for(int i =0; i< results.Count;i++)
        {
            if(current!= results[i].gameObject)
            {
                ExecuteEvents.Execute(results[i].gameObject, data,function);
                //RaycastAll后ugui会自己排序,如果你只想响应透下去的最近的一个响应,这里ExecuteEvents.Execute后直接break就行。
            }
        }
    }
 
 
}

UI组件添加事件监听 (AddListener)

btn.onclick.Addlistener(...);

当点击按钮时会触发OnPointerClick函数进而调用m_OnClick事件

alt alt

参考链接:https://blog.csdn.net/zhaocg00/article/details/118999234