Unity UGUI源码解析(一) EventSystem

一.引言

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

在这里插入图片描述

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

二.EventSystem作用

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

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

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

private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();

关于输入事件模块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<EventSystem> m_EventSystems = new List<EventSystem>();

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

    /// <summary>
    /// Return the current EventSystem.
    /// </summary>
    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<RaycastResult> 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中相关事件

点击事件
接口事件作用
IPointerEnterHandlerOnPointerEnter鼠标进入对象
IPointerExitHandlerOnPointerExit鼠标离开对象
IPointerDownHandlerOnPointerDown按下鼠标
IPointerClickHandlerOnPointerClick点中该对象,在OnPointerUp后发生
IPointerUpHandlerOnPointerUp松开鼠标
拖拉事件
接口事件作用
IBeginDragHandlerOnBeginDrag开始拖拽,必须同时实现IDragHandler
IDragHandlerOnDrag正在拖拽,每当移动一定距离,就发生一次拖拽事件
IEndDragHanlderOnEndDrag结束拖拽,必须同时实现IDragHandler
InitializePotentialDragHandlerOnInitializePotentialDrag可能发生拖拽,必须同时实现IDragHandler,在对象内点击就会发生
IDropHandlerOnDrop拖拉结束,拖拉开始的地方必须先实现IDragHandler
其他事件
接口事件作用
IScrollHandlerOnScroll操作鼠标中键的滚轮
ISelectHandlerOnSelect当EventSystem选中该对象,使用SetSelectedGameObject方法来选中
IDeselectHandlerOnDeselect不再选中该对象,点击对象外的地方就会变成不选中
IUpdateSelectedHandlerOnUpdateSelected当对象被选中,则每帧都会发生,对象被选中才会发生
ISubmitHandlerOnSubmit点击Submit键(默认Enter键),对象被选中才会发生
ICancelHandlerOnCancel点击Cancel键(默认Esc键),对象被选中才会发生
IMoveHandlerOnMove点击方向键,对象被选中才会发生
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity UGUI(User Interface)是Unity引擎用于创建用户界面的接口。UGUI提供了一系列的组件和功能,用于创建交互式的用户界面,包括按钮、文本框、滑动条、面板等。 以下是一些常用的UGUI接口: 1. Canvas(画布):Canvas是UGUI的根节点,用于容纳所有的UI元素。可以通过Canvas组件设置画布的渲染模式、分辨率适配等属性。 2. UI元素:UGUI提供了一系列的UI元素组件,如Text(文本)、Image(图片)、Button(按钮)、Slider(滑动条)、InputField(输入框)等。这些组件可以通过Inspector面板或脚本进行属性设置和事件绑定。 3. Layout组件:UGUI提供了Layout组件,用于自动调整UI元素的位置和大小。常用的Layout组件有HorizontalLayoutGroup(水平布局)、VerticalLayoutGroup(垂直布局)和GridLayoutGroup(网格布局)等。 4. EventSystem(事件系统):EventSystem用于处理用户输入事件,如点击、拖拽等。可以通过EventSystem组件设置事件的触发方式和优先级。 5. UI动画:UGUI支持UI元素的动画效果,可以通过Animator组件和Animation组件来实现。Animator组件可以控制UI元素的状态转换和过渡效果,而Animation组件可以实现基于关键帧的动画效果。 6. UI交互:UGUI提供了一些常用的UI交互功能,如按钮点击事件、滑动条数值改变事件等。可以通过脚本来监听和处理这些事件,实现与用户的交互。 以上只是UGUI接口的一部分,UGUI还提供了更多的功能和组件,可以根据具体需求进行学习和使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值