UGUI源码分析:从Button到逐行清楚事件检测的实现过程

系列

UGUI源码分析系列总览
相关前置:
UGUI EventSystem源码分析
UGUI源码分析:Selectable交互组件的基类


Button

BaseClass: Selectable

Interface: IPointerClickHandler,ISubmitHandler

Intro: UGUI按钮点击组件

  • IPointerClickHandler:点击事件的响应接口
  • ISubmitHandlerSubmit按键点击事件的响应接口,Submit是可以在Project Settings中的Input输入设置。当组件被选中时(“选中”的详细介绍请看Selectable)可响应Submit事件。
    在这里插入图片描述

Button,我们再熟悉不过的组件了。它完成了最简单的交互操作:点击。其实Button组件的源码非常简单,仅仅是实现了OnPointerClickOnSubmit两个事件的响应。这个估计使用过Button的人都已经非常清楚不过了。

public virtual void OnSubmit(BaseEventData eventData)
{
    Press();//执行注册方法的逻辑
    if (!IsActive() || !IsInteractable())
        return;
    DoStateTransition(SelectionState.Pressed, false);
    //因为Selectable中已经写了OnPointerDown、OnPointerUp时对应的状态变化了
    //但是对于Submit并没有结束的判断事件,所以依靠协程来执行状态变化
    StartCoroutine(OnFinishSubmit());
}
public virtual void OnPointerClick(PointerEventData eventData)
{
    if (eventData.button != PointerEventData.InputButton.Left)
        return;
    Press();//执行注册方法的逻辑
}
//OnPointerClick 与 OnSubmit 都会执行的响应操作
private void Press()
{
    if (!IsActive() || !IsInteractable())
        return;
    UISystemProfilerApi.AddMarker("Button.onClick", this);
    m_OnClick.Invoke(); // 执行注册的方法
}

码量非常少而功能单一,所以今天的源码分析到此就结束啦…
.
.
.

…是不是内容太少了点呢,作者我也是这么觉得的…

所以本篇文章的延伸内容就来了,友情提醒:前方有大量代码出没

Execute XXXHandler

对这个写法是不是很熟悉呢?作者之前的文章中也经常这么来提及(EventSystem中的处理接口)。那其实在之前 EventSystem 章节中已经分析了事件系统的执行的整体流程了,今天我们随着Button组件来深入地来分析具体的Handler是如何被检查并触发的。

在这里插入图片描述

STEP1.一切都是由EventSystem的Update开始的:

//EventSystem
protected virtual void Update()
{
    if (current != this)
        return;
    TickModules();//遍历并刷新所有的InputModules,更新Modules中的m_LastMousePosition、m_MousePosition           
    //省略中间遍历检查是否需要变更当前m_CurrentInputModule的部分
  	.....
  	//执行当前InputModule的Process,由此开始判断事件
    if (!changedModule && m_CurrentInputModule != null)
        m_CurrentInputModule.Process();
}

STEP2.InputModule先会进行对外设输入的检测,来更新导航或是确定操作。紧接着会开始触摸检测,若不存在触摸,则进行鼠标事件的检测,因为触摸事件的检测是鼠标检测的简化版,所有下面我们针对鼠标检测进行分析。

//StandaloneInputModule
public override void Process()
{
    if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
        return;
    //向当前选中的目标执行UpdateSelectedHandler,并返回是否执行了
    bool usedEvent = SendUpdateEventToSelectedObject();

    //若用导航的情况会执行MoveHandler与SubmitHandler,当执行成功某一项时停止
    if (eventSystem.sendNavigationEvents)
    {
        if (!usedEvent)
            usedEvent |= SendMoveEventToSelectedObject();
        if (!usedEvent)
            SendSubmitEventToSelectedObject();
    }
    //以上部分是用来检测键盘输入的部分,例如使用键盘方向键选择按钮、使用ENTER键执行Submit。
    //接着开始先进行触摸的事件检测,如果不存在触摸,则会进行鼠标的事件检测
    if (!ProcessTouchEvents() && input.mousePresent)
        ProcessMouseEvent();
}

STEP3.鼠标事件的检测过程,从中我们也能很清楚的了解到各个Handler的执行顺序(方法的复杂度在不断提升)

//StandaloneInputModule
protected void ProcessMouseEvent(int id)
{
    //实际上这个id也没有用,每次都是获取左中右三个按键的信息
    //这里包含了PointerEventData数据与ButtonState数据,前者主要记录事件相关的信息,后者记录鼠标按键的当前状态
    var mouseData = GetMousePointerEventData(id);
    var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData;
    m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;
    
	//执行鼠标按压的过程(根据buttonState来判断并执行 PointerDown PointerUp PointerClick Drop EndDrag 事件)
    ProcessMousePress(leftButtonData);
    
    //执行鼠标移动过程(根据pointerEvent判断并执行 PointerEnter PointerExit 事件)
    ProcessMove(leftButtonData.buttonData);
    
    //执行拖拽过程(根据pointerEvent判断并执行 BeginDrag Drag PointerUp 事件)
    ProcessDrag(leftButtonData.buttonData);
	
    //以下是对鼠标右键与中键的相同执行
	ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData);
	ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData);
	ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData);
	ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData);

    //检测是否存在滚动事件,参数来自于input.mouseScrollDelta,这里使用了leftButtonData,实际上scrollDelta三个ButtonData里都是一样的,因为在GetMousePointerEventData方法中其他两个buttonData都是Copy leftData:)
    if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f))
    {
        var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(leftButtonData.buttonData.pointerCurrentRaycast.gameObject);
        ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler);
    }
}

STEP4.深入其中,点击事件的判断。

  • 在按下的情况下:PointerDown会先被执行,其次会检查物体是否有DragHandler,如果存在则会执行InitializePotentialDrag,这个会在发生拖拽之前执行。
  • 在抬起的情况下(完成的点击操作):先会执行PointerUp,其次执行PointerClick,接着时Drop,最后时EndDrag。
//StandaloneInputModule
protected void ProcessMousePress(MouseButtonEventData data)
{
    var pointerEvent = data.buttonData;
    var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
    //判断当前是否是按下状态(都可以包含按下和抬起同帧情况)
    if (data.PressedThisFrame())
    {
        pointerEvent.eligibleForClick = true;
        pointerEvent.delta = Vector2.zero;
        pointerEvent.dragging = false;
        pointerEvent.useDragThreshold = true;
        pointerEvent.pressPosition = pointerEvent.position;
        pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;

        DeselectIfSelectionChanged(currentOverGo, pointerEvent);
        
        //搜索父级路径下是否有IPointerDownHandler组件并执行
        var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);

        //如果自身及父级路径下没有IPointerDownHandler,则检查该路径下的IPointerClickHandler。
        if (newPressed == null)
            newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);

        float time = Time.unscaledTime;
        //若按压物体为发生变化,更新点击信息
        if (newPressed == pointerEvent.lastPress)
        {
            var diffTime = time - pointerEvent.clickTime;
            if (diffTime < 0.3f)
                ++pointerEvent.clickCount;
            else
                pointerEvent.clickCount = 1;
            pointerEvent.clickTime = time;
        }
        else
        {
            pointerEvent.clickCount = 1;
        }
        pointerEvent.pointerPress = newPressed;
        pointerEvent.rawPointerPress = currentOverGo;
        pointerEvent.clickTime = time;

        pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
        //当存在IDragHandler时,先触发 initializePotentialDrag 事件 这个事件在BeginDrag之前
        if (pointerEvent.pointerDrag != null)
            ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);

        m_InputPointerEvent = pointerEvent;
    }

    //当抬起时(都可以包含按下和抬起同帧情况)
    if (data.ReleasedThisFrame())
    {
        // 最先执行PointerUp事件
        ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);

        var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);

        if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent.eligibleForClick)
        {
            // 其次才执行PointerClick事件
            ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler);
        }
        else if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
        {
            // Drop 事件会在 EndDrag 之前执行
            ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
        }

        pointerEvent.eligibleForClick = false;
        pointerEvent.pointerPress = null;
        pointerEvent.rawPointerPress = null;

        // 最后执行EndDrag 事件
        if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
            ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);

        pointerEvent.dragging = false;
        pointerEvent.pointerDrag = null;

        if (currentOverGo != pointerEvent.pointerEnter)
        {
            HandlePointerExitAndEnter(pointerEvent, null);
            HandlePointerExitAndEnter(pointerEvent, currentOverGo);
        }

        m_InputPointerEvent = pointerEvent;
    }
}
//PointerInputModule  检查鼠标按键状态
protected PointerEventData.FramePressState StateForMouseButton(int buttonId)
{
    var pressed = input.GetMouseButtonDown(buttonId); //按下
    var released = input.GetMouseButtonUp(buttonId); //抬起
    if (pressed && released)
        return PointerEventData.FramePressState.PressedAndReleased;
    if (pressed)
        return PointerEventData.FramePressState.Pressed;
    if (released)
        return PointerEventData.FramePressState.Released;
    return PointerEventData.FramePressState.NotChanged;//无变化
}

到此,输入事件的检测与执行流程已经分析完毕。当我们对EventSystem深入理解之后,我们便可以更好的去使用交互组件,并根据自己的需求修改扩展它们。(意思就是之后会出修改扩展组件的文章,提前挖坑:)


.
.
.
.
.


嗨,我是作者Vin129,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)


原创文章 39 获赞 59 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_28820675/article/details/105941286