UGUI source code analysis event system in Unity (2)-EventSystem component

UGUI source code analysis event system in Unity (2)-EventSystem component

Introducing our first protagonist today: EventSystem .

EventSystem is at the center of the entire event system, which is equivalent to the manager of the event system and is responsible for coordinating various modules.

It is a MonoBehavior , inherited from UIBehaviour, the unified parent class of UI module .

Adding any UI control in a scene will add an EventSystem object by default, which mounts the EventSystem component and a StandaloneInputModule (independent input module) component. For various events to take effect, at least one EventSystem is required in the scene . But there can also be multiple , but only one will take effect.

By default, Unity will find the input module from the object that mounts the EventSystem component and add it to its internal list for management and use.

main effect

From the official documentation and source code, EventSystem is mainly responsible for maintaining the input module and providing some configuration parameters for other event modules to use.

Therefore, a BaseInputModule list is maintained inside the EventSystem , and the currently effective input modules are saved. Each input module is updated in the life cycle function.Update

At the same time, it holds a static list, saves all EventSystems , and uses the first one in the list each time.

OnEnable/OnDisableAdd and remove EventSystem in lifecycle functions .

UIBehaviour

Most of the UI elements are MonoBehavior , which is not much different from the components we wrote ourselves. It’s just that Unity didn’t choose to inherit MonoBehavior directly, but abstracted a layer of abstract class UIBehaviour on it , which encapsulated and implemented part of the life cycle of MonoBehavior by default. Functions (eg protected virtual void Awake):

  • Awake
  • OnEnable
  • Start
  • OnDisable
  • OnDestroy
  • IsActive(Self)
  • Editor: OnValidate, Reset
  • OnRectTransformDimensionsChange
  • OnBeforeTransformParentChanged
  • OnTransformParentChanged
  • OnDidApplyAnimationProperties
  • OnCanvasGroupChanged
  • OnCanvasHierarchyChanged
  • IsDestroyed(Self)

Most of the interface comes from MonoBehavior , and the actual call is at the C/C++ layer, unfortunately we can't see it. The guess should be triggered around Canvas.

Main properties, fields and methods

The following is a brief description of the public attributes and methods of EventSystem . There are not many contents. According to the official description, EventSystem is more of a manager role, responsible for coordination and transfer, and is not mainly responsible for specific work.

panel properties

EventSystem has relatively few panel properties, and generally does not need to be used and modified.

  • First Selected: The object selected for the first time, you can specify an object before running, and select the object immediately after running, such as an input box
  • Send Navigation Events: Whether to allow sending navigation events, we will introduce it separately below
  • Drag Threshold: The tolerance area of ​​the drag operation (in pixels), from the official explanation, it seems that it should be the minimum displacement for judging the drag, generally keep the default

navigation event

Navigation events refer to: Move, Submit, Cancel.

These events Edit->ProjectSettings->Inputcan be searched and configured in , where the mobile event is Horizontal or Vertical, and the other two events can be corresponding according to their names.

insert image description here

The corresponding event declaration and test code are as follows ( EventTrigger can also be used ):

// IMoveHandler, ISubmitHandler, ICancelHandler

public class NavigationEventTest : MonoBehaviour, ISubmitHandler, ICancelHandler, IMoveHandler {
	public void OnSubmit(BaseEventData eventData) {
		Debug.LogError("eventData: " + eventData);
	}
	public void OnCancel(BaseEventData eventData) {
		Debug.LogError("onCancel: " + eventData);
	}
	public void OnMove(AxisEventData eventData) {
		Debug.LogError("onMove: " + eventData);
	}
}

I am mounting the script on an InputField. After the input is complete, press Enter twice to trigger the submit event, press ESC twice to trigger the cancel event, press Enter once and press the up, down, left, and right keys to trigger the movement. Yes Interested students can try it by themselves.

Input Module (BaseInputModule)

The main work of the event system is specifically responsible for the input module, and EventSystem plays the role of maintaining and driving the input module.

Below I gather the code related to the input module together for easy observation.

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

/// 当前生效的输入模块
private BaseInputModule m_CurrentInputModule;

// 收集当前对象上所有的输入模块, 由BaseInputModule->OnEnable/OnDisable触发
// 也就是说在BaseInputModule可用和不可用时会通过触发此方法来添加或者移除自己
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);
    }
}

// 触发输入模块的更新, 由当前激活的EventSystem在生命周期函数Update中触发
// 注意这里的只是更新数据, 而不过进行真正的事件处理, 真正的事件处理只有当前的输入模块才处理, 也就是说同时只有一个输入模块会处理事件
private void TickModules()
{
    for (var i = 0; i < m_SystemInputModules.Count; i++)
    {
        if (m_SystemInputModules[i] != null)
            m_SystemInputModules[i].UpdateModule();
    }
}

// 切换当前生效的输入模块
private void ChangeEventModule(BaseInputModule module)
{
    if (m_CurrentInputModule == module)
        return;

    if (m_CurrentInputModule != null)
        m_CurrentInputModule.DeactivateModule();

    if (module != null)
        module.ActivateModule();
    m_CurrentInputModule = module;
}

// 更新输入模块, 切换当前激活的输入模块并处理事件
protected virtual void Update()
{
    // 只有一个EventSystem能够处理事件
    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();
}

The comments should be more detailed, so I won't explain them further.

In short, it is to maintain all input modules, then switch the currently active input module at the appropriate time, and finally update the input module and use the current input module to process events in the Update of each frame.

Other properties and methods

ToString

In the running state, we can click the EventSystem object, and then see the various states of the current event system in the Inspector panel. As shown in the figure:

insert image description here

Move, click the mouse, etc. to see the data changes.

This is done through methods of EventSystem and PointerInputModule .Tostring

// EventSystem
public override string ToString()
{
    var sb = new StringBuilder();
    sb.AppendLine("<b>Selected:</b>" + currentSelectedGameObject);
    sb.AppendLine();
    sb.AppendLine();
    sb.AppendLine(m_CurrentInputModule != null ? m_CurrentInputModule.ToString() : "No module");
    return sb.ToString();
}

// PointerInputModule
public override string ToString()
{
    var sb = new StringBuilder("<b>Pointer Input Module of type: </b>" + GetType());
    sb.AppendLine();
    foreach (var pointer in m_PointerData)
    {
        if (pointer.Value == null)
            continue;
        sb.AppendLine("<B>Pointer:</b> " + pointer.Key);
        sb.AppendLine(pointer.Value.ToString());
    }
    return sb.ToString();
}

Simply put, it prints the details of the two components.

OnEnable/OnDisable

// 通过类字段来持有所有的EventSystem
private static List<EventSystem> m_EventSystems = new List<EventSystem>();

// 只有第一个EventSystem生效
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);
        }
    }
}

// 在每个EventSystem可用时添加到列表
protected override void OnEnable()
{
    base.OnEnable();
    m_EventSystems.Add(this);
}

// 在每个EventSystem不可用时从列表移除
protected override void OnDisable()
{
    if (m_CurrentInputModule != null)
    {
        m_CurrentInputModule.DeactivateModule();
        m_CurrentInputModule = null;
    }

    m_EventSystems.Remove(this);

    base.OnDisable();
}

SetSelectedGameObject

Set the current selection object and send events (invert and select)

// 当前选中对象
private GameObject m_CurrentSelected;

// 选中保护, 因为在反选和选中的过程中会触发事件, 需要在这个过程中对状态进行保护
private bool m_SelectionGuard;
public bool alreadySelecting
{
    get { return m_SelectionGuard; }
}

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;
}

Raycaster

Provides a ray detection and sorting method for other modules to call, we will introduce it when we use it, so we won’t paste the code here.

private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
{
    //...
}

private static readonly Comparison<RaycastResult> s_RaycastComparer = RaycastComparer;
public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
{
    //...
}

other

EventSystem also provides some properties and methods used by other modules, which are not the main part, so we won’t list them one by one here, and we will introduce them by the way when we use them.

Summarize

Today , I made a more detailed introduction to EventSystem following the official documentation and source code , and we understand and confirm the official statement.

EventSystem is indeed just a general manager and coordinator of the event system. Its content is not complicated. It mainly maintains the input module, and refreshes and drives the input module to handle specific events when appropriate. At the same time, it will also mark the current selection object The corresponding events are sent, and finally some properties and methods are provided for other modules to use.

In the next article, we will introduce the basic data structure used for encapsulation and delivery in the event system: EventData , which is the basis of subsequent modules.

Well, that's all for today's content, I hope it will be helpful to everyone.

Guess you like

Origin blog.csdn.net/woodengm/article/details/123416615