C#UGUI源码分析

以Button底层实现为例

Posted by JohnWayne on May 6, 2022

UGUI 源码地址 https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/index.html ——

UIBehavior类及其派生类

UGUI系统中所有的UI组件都是派生自抽象类UIBehavior.

比如Button组件派生自UIBehavior下的Selectable,Image组件派生自UIBehavior下的Graphic下的MaskableGraphic.

用四种方法实现点击逻辑

1.直接利用unity的Button组件界面的On click()绑定

缺点:只能左键单击,多人开发时仅通过代码看不出这里绑定了事件;

优点:直观,操作快捷。

2.通过代码里AddListener()

    private Button button;
    void Start()
    {
        button = GetComponent<Button>();
        button.onClick.AddListener(Action);
    }

这里关注一下onClick是什么,点进UI.Button看到。

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

找到m_OnClick的定义。

        public class ButtonClickedEvent : UnityEvent {}

        // Event delegates triggered on click.
        [FormerlySerializedAs("onClick")]
        [SerializeField]
        private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();

发现m_OnClick本质是UnityEvent,怪不得上面用的是AddListener,那么Invoke理论上应该在按下后触发,我们继续看。

        private void Press()
        {
            if (!IsActive() || !IsInteractable())
                return;

            UISystemProfilerApi.AddMarker("Button.onClick", this);
            m_OnClick.Invoke();
        }

果然在Press函数这里找到了Invoke(),表示按下后触发。

继续看点击事件,Button是Selectable的子类,并继承了IPointerClickHandler这个接口,要实现OnPointerClick方法。

public class Button : Selectable, IPointerClickHandler, ISubmitHandler
{
	...
		private void Press()
		{
			if (IsActive() && IsInteractable())
			{
				UISystemProfilerApi.AddMarker("Button.onClick", this);
				m_OnClick.Invoke();
			}
		}

		public virtual void OnPointerClick(PointerEventData eventData)
		{
			if (eventData.button == PointerEventData.InputButton.Left)
			{
				Press();
			}
		}

这里可以很明白的看出来只有左键单击,保证勾选激活,且可交互的条件下才会触发回调。 那么利用类似IPointerClickHandler的EventSystems可以拜托Button组件的束缚,给任何一个UI添加事件。

3.通过EventSystems添加事件

继承事件接口,并实现对应方法。 以“左右键单击”和“拖拽”为例。

using UnityEngine;
using UnityEngine.EventSystems;

public class Image_event : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler
{
    public void OnPointerClick(PointerEventData pointerEventData)
    {
        //Use this to tell when the user right-clicks on the Button
        if (pointerEventData.button == PointerEventData.InputButton.Right)
        {
            //Output to console the clicked GameObject's name and the following message. You can replace this with your own actions for when clicking the GameObject.
            Debug.Log(name + " Game Object Right Clicked!");
        }

        //Use this to tell when the user left-clicks on the Button
        if (pointerEventData.button == PointerEventData.InputButton.Left)
        {
            Debug.Log(name + " Game Object Left Clicked!");
        }
    }


    public void OnBeginDrag(PointerEventData pointerEventData)
    {
        Debug.Log("OnBeginDrag");
        if (pointerEventData.button == PointerEventData.InputButton.Right)
        {
            Debug.Log(name + " Game Object Right Draged!");
        }
        if (pointerEventData.button == PointerEventData.InputButton.Left)
        {
            Debug.Log(name + " Game Object Left Draged!");
        }
    }

    public void OnDrag(PointerEventData eventData)
    {
        Debug.Log("OnBeginDrag");
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        Debug.Log("OnBeginDrag");
    }
}

注意:其中IBeginDragHandler接口必须先实现IDragHandler 文档里注明了这点

4.利用Event Trigger组件

在组件上右键Edit Script进入EventTrigger.cs

    public class EventTrigger :
        MonoBehaviour,
        IPointerEnterHandler,
        IPointerExitHandler,
        IPointerDownHandler,
        IPointerUpHandler,
        IPointerClickHandler,
        IInitializePotentialDragHandler,
        IBeginDragHandler,
        IDragHandler,
        IEndDragHandler,
        IDropHandler,
        IScrollHandler,
        IUpdateSelectedHandler,
        ISelectHandler,
        IDeselectHandler,
        IMoveHandler,
        ISubmitHandler,
        ICancelHandler
        ...
        private void Execute(EventTriggerType id, BaseEventData eventData)
        {
            var triggerCount = triggers.Count;

            for (int i = 0, imax = triggers.Count; i < imax; ++i)
            {
                var ent = triggers[i];
                if (ent.eventID == id && ent.callback != null)
                    ent.callback.Invoke(eventData);
            }
        }
        ...
        /// <summary>
        /// Called before a drag is started.
        /// </summary>
        public virtual void OnBeginDrag(PointerEventData eventData)
        {
            Execute(EventTriggerType.BeginDrag, eventData);
        }

EventTrigger组件本质上就是封装了EventSystems事件。

总结

通过添加点击/拖拽这个应用为例,用这四种方法的递进和对比,由浅入深地阐述了EventSystems的底层原理。

还有几个知识点没有仔细研究,如挂在Canvas上的Graphic Raycaster 和 挂在EventSystem物体下的Input Module,在Input组件里轮询,发现module改变了再进入EventSystem处理,最后唤醒回调。

提出问题

Q:UnityEvent也有监听和触发,那和委托有什么区别?

A:网上的回答

作者 [张巍]

2022 年 05月 06日