UGUI – Analyzing the UGUI framework from the perspective of Button (Part 2)

In the last article, we stopped at the search for PointerEventData data and traced it back to the PorcessMousePress() method. Finally, we found that the source of the data structure was constructed in the ProcessMouseEvent(int id) function. The source code is as follows

1Preliminary structure of PointerEventData data

//StandaloneInputModule| void ProcessMouseEvent(int id)
var mouseData = GetMousePointerEventData(id);
var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData; 2
m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;three

1.1 The data structure uses the RayCaster system

We traced the GetMousePointerEventData(id) method and found that this method is in its parent class PointerInputModule, and its return value is of MouseState type. When we enter, I personally think the following sentences are important:

//PointerInoutModule| MouseState GetMousePointerEventData(int id)
eventSystem.RaycastAll(leftData, m_RaycastResultCache);
var raycast = FindFirstRaycast(m_RaycastResultCache);
leftData.pointerCurrentRaycast = raycast;

The specific content of the first sentence is to find a Raycaster from RayCasterManager (what is obtained here is GraphicRaycaster, how to get it will be explained below, it is assumed here that you have already figured out how to get it). Then a ray is fired based on the mouse position. If the ray hits the UI element, a series of information will be added to m_RaycastResultCache. The second sentence is to simply select from m_RaycastResultCache the ray that the gameObject passed by the first ray is not null. Since it has been sorted in GraphicRaycaster, here we simply take out an element. The third sentence is also more critical. It adds the ray currently pointed by the mouse to our usage data. Some of the following content is the copied PointerData assigned to the right button and middle button.
A very important point needs to be mentioned here. These data are not complete. The truly complete data needs to be constructed in the pressed branch in the ProcessMousePress function.
Summarize:
After one, two, and three, we finally know that the data (incomplete) is actually obtained here. It emits a ray through GraphicRaycaster. When it hits the UI element (needs to be inherited from Graphic), the ray-related content is obtained and stored, and then the row is The first ray result in order is given to m_CurrentFocusedGameObject in PointerEventData. At this point, we know which Button in the scene we clicked. Then recall the previous article, we already know that there is relevant binding in the Execute class, and then the bound event is actually called back in ProcessMousePress. After the previous article and the first half of this article, assuming that we have understood that when we want to click a Button, the mouse will move to the UI element where the Button is located. At this time, our GraphicRaycaster will come into play and it will emit a ray. The ray will record the information of the passing UI elements, and PointerEventData will take the first sorted ray and store it as pointerCurrentRaycast, and then actually call the bound event in the ReleaseMouse method in ProcessMousePress.

2Why data is constructed

The next step is to figure out why the program runs into the data construction method? We looked up the source and found that it started in the Update function of EventSystem. If there is Update, it will be called every frame. Let’s see what Update does specifically. When it actually calls StandaloneInputModule, it will first call its own TickModules() method, this method will call the UpdateModule() function of each element in m_SystemInputModules. Where does the value in m_SystemInputModules come from? The OnEnable function in BaseInputModule will call the UpdateModules method in EventSystem, which means that m_SystemInputModules will Assign a value in the OnEnable cycle. The assigned value is actually the StandaloneInputModule component mounted on the EventSystem in the Scene panel in Unity.
When executing UpdateModule() of StandaloneInputModule, it needs to execute the following two statements to cache and update the position of the mouse. Input is a component automatically added at runtime. Only with it can the position of the mouse be known at all times.

m_LastMousePosition = m_MousePosition;
m_MousePosition = input.mousePosition;

Next, it will be judged whether the current input type has been changed. Generally, it will not change. It will also be judged whether the current input model is empty, which is generally not empty, because a StandaloneInputModule has been added during the OnEnable cycle. Finally, it will Enter the Process function of StandaloeInputModule under one judgment. Finally connected to the overall process of the previous article.
Summarize
After the above content, we understand that the EventSystem will call the Process of the input model, and in the previous article we learned that the click event cannot be initiated without the binding callback of ExecuteEvent. The next step is the highlight, that is, when the Button is clicked, there will be The two actions of pressing and lifting and the entire click action

3. Occurrence of click event

After getting the preliminary data, we continued going down and entered the ProcessMousePress method, and found that this function has two branches, one is to press in this frame, and the other is to lift in this frame.

3.1 Mouse press corresponds to if(data.PressedThisFrame())

Let’s look at the pressed branch first. Here are 6 more important statements:

//Assign data to the ray to handle the event
pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
//Change the currently selected object of eventSystem. If the selected object (the object here should have the ability to handle ISelectHandler) is different from the currently selected object
//The first thing selected here is Text, but Text does not have the ability, so it will search upward until it finds that Button has this ability, so it will compare Button with the currently selected object.
DeselectIfSelectionChanged(currentOverGo, pointerEvent);
//The callback of the pressed event bound in ExecuteEvent will be called here, specifically the change of the color of Button pressed.
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
//Here we will look for objects that can call the Click ability. We find Button to prepare for the next frame to lift and call the corresponding callback.
var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
//Assign values to these two data and prepare for lifting
pointerEvent.pointerPress = newPressed;
pointerEvent.pointerClick = newClick;

3.2 Mouse lifting corresponds to if(data.ReleasedThisFrame())

There is only one ReleaseMouse(pointerEvent, currentOverGo). Let me explain here. If the Button is pressed and then lifted outside the Button, the currentOverGo here will be null. Although the lift event will be executed, the click event will not be executed. Here are a few more important sentences

//This will call the lift event. If it has the ability, the Button will mainly use the color gradient here.
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
//This is to find whether currentGo and its parent class have the ability to handle Click. If so, return currentGo or its parent object.
var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
//This if statement will actually call the function bound to the Button. The specific details will not be described in detail here.
if (pointerEvent.pointerPress == pointerUpHandler & amp; & amp; pointerEvent.eligibleForClick)
{
    ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler);
}
//This will also determine whether you have left the current object. If you leave, the exit event and the new entry event will be called.
if (currentOverGo != pointerEvent.pointerEnter)
{
    HandlePointerExitAndEnter(pointerEvent, null);
    HandlePointerExitAndEnter(pointerEvent, currentOverGo);
}

In summary, the general process of clicking the button with the mouse and triggering the function bound to the button is like this. Going down, there are functions such as ProcessMove, but the truth about the button click that we want to discuss has surfaced. You can debug and read some details by yourself. Source code. During this debugging process, I think we have understood the collaboration of several major UGUI systems.

Summary: The process of Button clicking is mainly carried out in InputModule, and EventSystem is responsible for driving InputModule to operate. InputModule will construct the data. During the construction process, the ray emitter management center RaycasterManager and GraphicRaycaster are involved to determine whether the ray has passed through. Which objects, and then the InputModule will send commands to ExecuteEvent at the appropriate time based on the data to call back the corresponding method. When the ExecuteEvent callback method will involve changes in the color of the Button picture, etc., this in turn involves the Graphic class.

Conclusion: This is just a macroscopic view of the collaboration of various UGUI systems from the perspective of Button. Many details may not be in place, such as how the ExecuteEvent.Execut function triggers the callback, and how the color changes when the Button is pressed. ‘s and so on. If we want to get down to the details, we still have to look at many more detailed source code analyzes on the Internet. Of course, it is best to look at the source code yourself.