Unity UGUI源码解析(一) EventSystem

一.引言

当我们在Unity中创建一个Canvas时,编辑器会默认给我们创建一个EventSystem,其上有两个组件EventSystemStandaloneInputModule如下图

Unity UGUI源码解析(一) EventSystem_第1张图片

那EventSystem到底是用来干什么的呢?我们找到UGUI底层的源码来一探究竟!UGUI源码

二.EventSystem作用

EventSystem在UGUI源码中属于事件逻辑处理模块。所有UI事件都是通过EventSystem类中通过轮询检测到并作相应事件执行,通过调用输入事件检测模块和检测碰撞模块来形成自己的主逻辑。

简要分析EventSystem类中大致进行的处理操作,由于涉及到源码中各类之间功能的互相调用
所以不能在当前EventSystem章中全部细讲。

那我们现在开始吧!
首先定义了一个列表 m_SystemInputModules用来存储输入事件模块

private List m_SystemInputModules = new List();

关于输入事件模块BaseInputModule 会在以后单独开一章去讲解
这里我们可以先大致了解一下BaseInputModule类是一个抽象基类,提供了一些空接口和基本变量。

其中PointerInputModule继承自BaseInputModule,并且在它基础上扩展了关于点位的输入逻辑,也增加了输入类型和状态。

StandaloneInputModule:适用于主机pc上的一些键盘鼠标输入
TouchInputModule:适用于手机上触摸输入
这两个类均继承自PointerInputModule。

所以 m_SystemInputModules中存储的应该是StandaloneInputModule或者TouchInputModule类列表
接着定义了一个BaseInputModule的对象 m_CurrentInputModule 用于获得当前输入模块

private BaseInputModule m_CurrentInputModule;

继续 定义了一个静态static的列表 m_EventSystems

private  static List m_EventSystems = new List();

虽然说是列表,但其实在Unity场景下我们只能有且仅有一个EventSystem,所以下面那段代码就是表示了这个意思:

    /// 
    /// Return the current EventSystem.
    /// 
    public static EventSystem current
    {
        get { return m_EventSystems.Count > 0 ? m_EventSystems[0] : null; }
        set
        {
            int index = m_EventSystems.IndexOf(value);
            if (index >= 0)
            {
                m_EventSystems.RemoveAt(index);
                m_EventSystems.Insert(0, value);
            }
        }
    }

接下来就是一堆属性的get set获取,这部分大家可以自行看下源码

终于到函数了.EventSystem中关键的函数UpdateModules(),用于重新更换m_SystemInputModule的列表信息。
通过遍历m_SystemInputModules 一旦列表中模块不存在或者未处于激活状态就进行移除

    public void UpdateModules()
    {
        GetComponents(m_SystemInputModules);
        for (int i = m_SystemInputModules.Count - 1; i >= 0; i--)
        {
            if (m_SystemInputModules[i] && m_SystemInputModules[i].IsActive())
                continue;

            m_SystemInputModules.RemoveAt(i);
        }
    }

与这个UpdateModules()相类似的函数TickModules(),它则是用于更新列表中对应模块的具体信息,可自行查找源码中StandaloneInputModule中找到具体更新函数,这边暂时不细讲。

    private void TickModules()
    {
        for (var i = 0; i < m_SystemInputModules.Count; i++)
        {
            if (m_SystemInputModules[i] != null)
                m_SystemInputModules[i].UpdateModule();
        }
    }

EventSystem中Update()则是会一直执行TickModules(),并且遍历m_SystemInputModules 通过逻辑返回找到m_CurrentInputModule 当前输入模块 并执行**Process()**用于执行模块中相应的事件,具体例子可以去StandaloneInputModule中找到Process()函数自行查看

    protected virtual void Update()
    {
        if (current != this)
            return;
        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())
                {
                    ChangeEventModule(module);
                    changedModule = true;
                    break;
                }
            }
        }

        if (!changedModule && m_CurrentInputModule != null)
            m_CurrentInputModule.Process();
    }

还有一个函数大家也不会陌生 IsPointerOverGameObject() 这个函数我们经常用来判断是否点击到UI上。
源码中具体是在PointerInputModule中实现,这里暂时不细讲,主要是根据传入一个指针id(默认是鼠标左键-1)来返回对应的物体信息。

我们在实际运用中经常会遇到UI和场景中的可交互物体发生冲突等原因,这个时候我们就可以利用IsPointerOverGameObject()来进行判断点击到的是UI还是别的

    void Update()
    {
        // 判断是否点击了鼠标左键
		if(Input.GetMouseButtonDown(0))
		{
            // 判断当前鼠标指针是否处于UI物体上
			if(EventSystem.current.IsPointerOverGameObject())
			{
				Debug.Log("点击到了UI上");
			}
		}
    }

剩余一些函数SetSelectedGameObject():用于设置当前物体被选中,并且发送OnDeselect事件给之前那个被选中的物体,发送OnSelect事件给当前物体。
该函数通过静态对象EventSystem.current 被其他脚本如:InputField、Selectable所调用

    public void SetSelectedGameObject(GameObject selected, BaseEventData pointer)
    {
        if (m_SelectionGuard)
        {
            Debug.LogError("Attempting to select " + selected +  "while already selecting an object.");
            return;
        }

        m_SelectionGuard = true;
        if (selected == m_CurrentSelected)
        {
            m_SelectionGuard = false;
            return;
        }

        // Debug.Log("Selection: new (" + selected + ") old (" + m_CurrentSelected + ")");
        ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler);
        m_CurrentSelected = selected;
        ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler);
        m_SelectionGuard = false;
    }

这里的m_SelectionGuard默认是false的,并且查看了一下它的引用也都在这个函数中了。
所以正常思路来说第一句if语句是走不进去的。

所以我想的是如果设计到多线程,该函数在被引用的时候,由于线程不安全导致了一个函数执行到m_SelctionGuard = true。
而另一个函数刚刚开始执行第一句if语句,判断为true,进入该语句块中。
不知道这样考虑的对不对,希望看过源码的各路神仙可以帮忙解答一下。

RaycastAll() 这个函数我个人理解就是更新当前射线检测的结果,并对射线碰撞到的所有物体进行由近至远的排序。这个函数在PointerInputModule中被调用,用来获取当前最先被射线检测到物体(即最近的)

    public void RaycastAll(PointerEventData eventData, List raycastResults)
    {
        raycastResults.Clear();
        var modules = RaycasterManager.GetRaycasters();
        for (int i = 0; i < modules.Count; ++i)
        {
            var module = modules[i];
            if (module == null || !module.IsActive())
                continue;

            module.Raycast(eventData, raycastResults);
        }

        raycastResults.Sort(s_RaycastComparer);
    }

基本上在源码EventSystem中主要功能都介绍完了。EventSystem类作为事件逻辑处理模块中重要的类,主要是处理输入事件检测模块,对于模块列表信息进行轮询更新,同时获取到当前输入模块后,对该模块进行Process()执行对应事件。其次还有获取射线碰撞检测结果,供PointerInputModule来获取当前最近的碰撞检测对象,以供执行对应事件。

总结:

EventSystem类似于一个中转站,和许多模块一起共同协作,定义的函数用来处理主逻辑,许多共有属性多被其他模块或类中进行赋值调用。

最后附上Unity中相关事件

点击事件
接口 事件 作用
IPointerEnterHandler OnPointerEnter 鼠标进入对象
IPointerExitHandler OnPointerExit 鼠标离开对象
IPointerDownHandler OnPointerDown 按下鼠标
IPointerClickHandler OnPointerClick 点中该对象,在OnPointerUp后发生
IPointerUpHandler OnPointerUp 松开鼠标
拖拉事件
接口 事件 作用
IBeginDragHandler OnBeginDrag 开始拖拽,必须同时实现IDragHandler
IDragHandler OnDrag 正在拖拽,每当移动一定距离,就发生一次拖拽事件
IEndDragHanlder OnEndDrag 结束拖拽,必须同时实现IDragHandler
InitializePotentialDragHandler OnInitializePotentialDrag 可能发生拖拽,必须同时实现IDragHandler,在对象内点击就会发生
IDropHandler OnDrop 拖拉结束,拖拉开始的地方必须先实现IDragHandler
其他事件
接口 事件 作用
IScrollHandler OnScroll 操作鼠标中键的滚轮
ISelectHandler OnSelect 当EventSystem选中该对象,使用SetSelectedGameObject方法来选中
IDeselectHandler OnDeselect 不再选中该对象,点击对象外的地方就会变成不选中
IUpdateSelectedHandler OnUpdateSelected 当对象被选中,则每帧都会发生,对象被选中才会发生
ISubmitHandler OnSubmit 点击Submit键(默认Enter键),对象被选中才会发生
ICancelHandler OnCancel 点击Cancel键(默认Esc键),对象被选中才会发生
IMoveHandler OnMove 点击方向键,对象被选中才会发生

你可能感兴趣的:(Unity3d学习,Unity,UGUI,EventSystem)