内容会持续更新,有错误的地方欢迎指正,谢谢!
该文章 补充部分 参考了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的一些底层实现大致的原理和思路