UGUI源码学习之Button

内容会持续更新,有错误的地方欢迎指正,谢谢!

该文章 补充部分 参考了https://blog.csdn.net/a237653639/article/details/50804633,感谢原作者。

声明:由于官方只公开了UGUI源码的C#封装的这层,并未公开C++底层代码,不过看看这些C#代码也不错

UIBehavior类及其派生类的类图如下:

这里写图片描述
可以看到在UGUI系统中所有的UI组件都派生自抽象类UIBehavior,里面定义的抽象方法是每个Scene里的对象几乎都有的,如经常用到的Start(),Awake()等等。UIBehavior总共有14个派生类(图中没有显示),其中今天要看的Button被归为Selectable的子类,即“可选”的意思,而Image、Text、RawImage则被归为Graphic的子类(图中没有显示),写到这里我们已经可以看出编写者的设计思路了。

Selectable:实现了一堆的接口,都是用来处理用户交互的。继承自该类的子类就都继承了这些交互的功能,所有的UGUI交互事件都是靠EventSystem来驱动的。

现在再看看Button的源码:(你将知道自己注册的Button事件是在哪里调用的,经过了哪些流程)

using System;
using System.Collections;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
    [AddComponentMenu("UI/Button", 30)]
    public class Button : Selectable, IPointerClickHandler, ISubmitHandler
    {
    [Serializable]
    public class ButtonClickedEvent : UnityEvent {}
    // Event delegates triggered on click.
    //首先有一个为ButtonClickedEvent嵌套类型的m_OnClick字段,
    //ButtonClickedEvent继承自UnityEvent,它是Unity的事件类。
    [FormerlySerializedAs("onClick")]
    [SerializeField]
    private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();
    protected Button()
    {}

    public ButtonClickedEvent onClick
    {
        get { return m_OnClick; }
        set { m_OnClick = value; }
    }

    //方法里面也很简单,如果Button组件激活或者启用就调用m_OnClick的invoke方法,
    //可为什么是私有的呢,这不方便调用啊?不急,继续往下看
    private void Press()
    {
        if (!IsActive() || !IsInteractable())
                return;
        m_OnClick.Invoke();//我们在AddListener中注册的函数就是在这里调用的
    }

    //OnPointerClick实现了IPointerClickHandler接口中定义的方法,
    //方法内部进行了判断,仅仅只有按下左键时才调用Press方法
    public virtual void OnPointerClick(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
                return;
        Press();
    }

    //OnSubmit也是类似的,但它实现的是ISubmitHandler接口的方法,
    //按下提交按钮时(默认Enter)调用Press方法
    public virtual void OnSubmit(BaseEventData eventData)
    {
        Press();
        // if we get set disabled during the press
        // don't run the coroutine.
        if (!IsActive() || !IsInteractable())
                return;
        //DoStateTransition和StartCoroutine是用来实现按钮触发的渐隐渐褪画面效果的
        DoStateTransition(SelectionState.Pressed, false);
        StartCoroutine(OnFinishSubmit());
     }

    private IEnumerator OnFinishSubmit()
    {
        var fadeTime = colors.fadeDuration;
        var elapsedTime = 0f;
        while (elapsedTime < fadeTime)
        {
            elapsedTime += Time.unscaledDeltaTime;
            yield return null;
        }
        DoStateTransition(currentSelectionState, false);
    }
}

补充:是谁来调用 OnPointerClick( ) 呢?那就让我们来开始回溯:

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

最后回溯到了EventSystem的Update函数中。那重点就是由EventSystem.Update()中调用的Process函数开始,这个Process函数就是接收并处理我们的输入事件,在Update中不停地刷(检测),看有没有需要处理的事件。

我们”查找所有引用”发现这是BaseInputModule中的抽象函数,其”孙子”StandaloneInputModule和TouchInputModule实现了该函数。我们是做手游的嘛,所以重点放在TouchInputModule上。

这里写图片描述

由注释可知,我们经常调用的是ProcessTouchEvents()这个函数。

这里写图片描述

如果没有输入,Input.touchCount就为0。而一旦有了输入,就立即开始各种判断处理。

回到我们关心的话题,我们注册的回调函数好久开始调用呢??

那关键就是下面的这个handler是不是我们的Button脚本了(这里用接口来接收,Button实现了IPointerClickHandler)

这里写图片描述

如果handler是我们的Button,那EventSystem怎么得到它的呢?答案如下图。

这里写图片描述

最终我们的注册函数就是在functor中调用了。以上就是我们整个UGUI与用户交互的调用流程了(其他接口也类似)。

这里写图片描述

收获:
1. 接口和类的设计以及代码命名规范都做得很好
2. 了解了Button的一些底层实现大致的原理和思路

猜你喜欢

转载自blog.csdn.net/BillCYJ/article/details/79879916