UGUI 源码精读1 (UIBehaviour)

2020-10-11  本文已影响0人  烂醉花间dlitf

源码位置

目录结构

可以新建一个项目,然后在 Packages/Unity UI 里面看到目录结构。


Unity UI 的目录结构

类/接口

如何找到一个接口或者类?

因为 UGUI 的源文件是在Package 里面,所以在 VS 里面打开之后都是杂项文件,那么对于我们寻找类与类之间的关系就很不方便。
比如对于 Graphic (定义如下) 而言,想找到 ICanvasElement 所在的文件,然后会发现并不能通过按住 Ctrl 再点击鼠标来定位到该接口声明的位置,而且也没有名为 ICanvasElement 的文件名

public abstract class Graphic
        : UIBehaviour,
          ICanvasElement

TODO
第三种方法会导致 create 里面没有 UI 这一栏。

参考
Unity打开Package目录下cs代码显示杂项文件的解决办法

UIBehaviour

位置:RunTime\EventSystem\UIBehaviour.cs
里面有很多虚函数,Unity Monobehaviour 里面可以看到,比如 OnRectTransformDimensionsChange 这种不是很熟悉的,其实是跟 StartUpdate 一样的。下面是源文件,中文注释是自己写的。

*OnRectTransformDimensionsChangeOnBeforeTransformParentChangedOnDidApplyAnimationProperties 是在 Unity Monobehaviour 里面找不到的,合理怀疑是官方文档没有更新,因为 OnBeforeTransformParentChanged() 上面说看 MonoBehaviour.OnBeforeTransformParentChanged ,但 Unity MonoBehaviour 里面并没有这个函数

namespace UnityEngine.EventSystems
{
    /// <summary>
    /// Base behaviour that has protected implementations of Unity lifecycle functions.
    /// </summary>

   // 抽象类,虽然里面没有抽象函数,但是申明为抽象类可以防止被实例化
    public abstract class UIBehaviour : MonoBehaviour
    {
        // 虚函数,可以被重写,也可以不被重写,相较于抽象函数,没有强制性(抽象函数子类必须要实现)
        protected virtual void Awake()
        {}

        protected virtual void OnEnable()
        {}

        protected virtual void Start()
        {}

        protected virtual void OnDisable()
        {}

        protected virtual void OnDestroy()
        {}

        /// <summary>
        /// Returns true if the GameObject and the Component are active.
        /// </summary>
        // MonoBehaviour 里面没有这个函数,所以是从 UIBehaviour 才开始有的。
        public virtual bool IsActive()
        {
            return isActiveAndEnabled;
        }

#if UNITY_EDITOR
        protected virtual void OnValidate()
        {}

        protected virtual void Reset()
        {}
#endif
        /// <summary>
        /// This callback is called if an associated RectTransform has its dimensions changed. The call is also made to all child rect transforms, even if the child transform itself doesn't change - as it could have, depending on its anchoring.
        /// </summary>
        protected virtual void OnRectTransformDimensionsChange()
        {}

        protected virtual void OnBeforeTransformParentChanged()
        {}

        protected virtual void OnTransformParentChanged()
        {}

        protected virtual void OnDidApplyAnimationProperties()
        {}

        protected virtual void OnCanvasGroupChanged()
        {}

        /// <summary>
        /// Called when the state of the parent Canvas is changed.
        /// </summary>
        protected virtual void OnCanvasHierarchyChanged()
        {}

        /// <summary>
        /// Returns true if the native representation of the behaviour has been destroyed.
        /// </summary>
        /// <remarks>
        /// When a parent canvas is either enabled, disabled or a nested canvas's OverrideSorting is changed this function is called. You can for example use this to modify objects below a canvas that may depend on a parent canvas - for example, if a canvas is disabled you may want to halt some processing of a UI element.
        /// </remarks>
        public bool IsDestroyed()
        {
            // Workaround for Unity native side of the object
            // having been destroyed but accessing via interface
            // won't call the overloaded ==
            return this == null;
        }
    }
}

现在做一个小实验,也就是在每个函数里面加上 Debug.log("函数名") ,可以看出各个虚函数都是什么时候调用的。

OnValidate()

介绍:当脚本被加载或者 Inspector 面板的值出现变化的时候会被调用,你可以通过它来保证 Inspector 上的一个值固定在一个范围之内,并且这个回调函数只有在编辑器模式下在会被调用,所以使用的时候最好用 #if UNITY_EDITOR#endif 包裹住。

#if UNITY_EDITOR
    /// <summary>
    /// This function is called when the script is loaded or a value is changed in the Inspector (Called in the editor only).
    /// You can use this to ensure that when you modify data in an editor, that data stays within a certain range.
    /// </summary>
    private void OnValidate()
    {
        Debug.Log("OnValidate");
    }
#endif

现象:

Reset()

介绍:将脚本恢复为默认值。同样的,使用的时候最好用 #if UNITY_EDITOR#endif 包裹住。

#if UNITY_EDITOR
    /// <summary>
    /// Reset to default values.
    /// </summary>
    private void Reset()
    {
        Debug.Log("Reset");
    }
#endif

现象:

OnRectTransformDimensionsChange()

介绍:当 RectTransform 发生改变的时候被调用。

    /// <summary>
    /// This callback is called if an associated RectTransform has its dimensions changed. 
    /// The call is also made to all child rect transforms, even if the child transform itself doesn't change - as it could have, 
    /// depending on its anchoring.
    /// </summary>
    private void OnRectTransformDimensionsChange()
    {
        Debug.Log("OnRectTransformDimensionsChange");
    }

现象:

OnBeforeTransformParentChanged() & OnTransformParentChanged()

介绍:当脚本所在的物体的的父物体发生改变,或者父物体的父物体发生改变时会被调用。OnBeforeTransformParentChanged() 发生在 OnTransformParentChanged() 前。可以理解为 Update()LateUpdate() 的关系。

    protected virtual void OnBeforeTransformParentChanged()
    {
        Debug.Log("OnBeforeTransformParentChanged");
    }

    /// <summary>
    /// This function is called when the parent property of the transform of the GameObject has changed.
    /// </summary>
    protected virtual void OnTransformParentChanged()
    {
        Debug.Log("OnTransformParentChanged");
    }
OnTransformParentChanged.gif

OnCanvasHierarchyChanged()

介绍:当 Canvas 的可用性发生改变的时候调用。(官方介绍说是父物体的 Canvas 发生变化,但是经实验发现即使该脚本和 Canvas 在同一个物体上也会被调用。)

 /// <summary>
    /// Called when the state of the parent Canvas is changed.
    /// </summary>
    protected virtual void OnCanvasHierarchyChanged()
    {
        Debug.Log("OnCanvasHierarchyChanged");
    }

OnDidApplyAnimationProperties()

介绍:当物体的属性被 Animation 修改的时候会被调用。

using UnityEngine;
public class Test : MonoBehaviour
{
    public int MyInt;
    private void Update()
    {
        this.transform.localScale = new Vector3(MyInt,MyInt,MyInt);
    }
    protected virtual void OnDidApplyAnimationProperties()
    {
        Debug.Log("OnDidApplyAnimationProperties");
    }
}

现象:

public class Test : GridLayoutGroup
{
    protected override void OnDidApplyAnimationProperties()
    {
        base.OnDidApplyAnimationProperties();
        Debug.Log("OnDidApplyAnimationProperties");
    }
}

或者

public class Test : GridLayoutGroup
{
    protected new void OnDidApplyAnimationProperties()
    {
        Debug.Log("OnDidApplyAnimationProperties");
    }
}

然后在编辑模式下使用动画修改 Test 脚本的值来测试。


现象1,2 现象2,3

总结:LayoutGroup.OnDidApplyAnimationProperties 虽然也有实现这个函数,但认为它依然是通过 MonoBehavior 来进行回调的,就像 Start() 一样。

OnCanvasGroupChanged()

介绍:父物体或者自身的 Canvas Group 组件发生变化(包括值改变或者可用性改变)时被调用。

protected virtual void OnCanvasGroupChanged()
{
    Debug.Log("OnCanvasGroupChanged");
}

现象:

结论

上面这些方法的实验,基本上都是直接或者间接继承了 MonoBehaviour 来完成的,所以我也猜测不管在官方的 MonoBehaviour 里面有没有出现某一个方法,它都是由 MonoBehaviour 来调用的,具体是如何被调用的,可以参考 Unity3d是如何调用MonoBehaviour子类中的Start等方法的?

参考(致谢)

UGUI系統研究講解-----》UIBehaviour功能說明

ICanvasElement

位置:Runtime\UI\Core\CanvasUpdateRegistry.cs

    public interface ICanvasElement
    {
        /// <summary>
        /// Rebuild the element for the given stage.
        /// </summary>
        /// <param name="executing">The current CanvasUpdate stage being rebuild.</param>
        void Rebuild(CanvasUpdate executing);

        /// <summary>
        /// Get the transform associated with the ICanvasElement.
        /// </summary>
        Transform transform { get; }

        /// <summary>
        /// Callback sent when this ICanvasElement has completed layout.
        /// </summary>
        void LayoutComplete();

        /// <summary>
        /// Callback sent when this ICanvasElement has completed Graphic rebuild.
        /// </summary>
        void GraphicUpdateComplete();

        /// <summary>
        /// Used if the native representation has been destroyed.
        /// </summary>
        /// <returns>Return true if the element is considered destroyed.</returns>
        bool IsDestroyed();
    }

GraphicRegistry

位置:Runtime\UI\Core\GraphicRegistry.cs

using System.Collections.Generic;
using UnityEngine.UI.Collections;

namespace UnityEngine.UI
{
    /// <summary>
    ///   Registry which maps a Graphic to the canvas it belongs to.
    /// </summary>
    public class GraphicRegistry
    {
        private static GraphicRegistry s_Instance;

        private readonly Dictionary<Canvas, IndexedSet<Graphic>> m_Graphics = new Dictionary<Canvas, IndexedSet<Graphic>>();

        protected GraphicRegistry()
        {
            // Avoid runtime generation of these types. Some platforms are AOT only and do not support
            // JIT. What's more we actually create a instance of the required types instead of
            // just declaring an unused variable which may be optimized away by some compilers (Mono vs MS).

            // See: 877060

            System.GC.KeepAlive(new Dictionary<Graphic, int>());
            System.GC.KeepAlive(new Dictionary<ICanvasElement, int>());
            System.GC.KeepAlive(new Dictionary<IClipper, int>());
        }

        /// <summary>
        /// The singleton instance of the GraphicRegistry. Creates a new instance if it does not exist.
        /// </summary>
        public static GraphicRegistry instance
        {
            get
            {
                if (s_Instance == null)
                    s_Instance = new GraphicRegistry();
                return s_Instance;
            }
        }

        /// <summary>
        /// Associates a Graphic with a Canvas and stores this association in the registry.
        /// </summary>
        /// <param name="c">The canvas being associated with the Graphic.</param>
        /// <param name="graphic">The Graphic being associated with the Canvas.</param>
        public static void RegisterGraphicForCanvas(Canvas c, Graphic graphic)
        {
            if (c == null)
                return;

            IndexedSet<Graphic> graphics;
            instance.m_Graphics.TryGetValue(c, out graphics);

            if (graphics != null)
            {
                graphics.AddUnique(graphic);
                return;
            }

            // Dont need to AddUnique as we know its the only item in the list
            graphics = new IndexedSet<Graphic>();
            graphics.Add(graphic);
            instance.m_Graphics.Add(c, graphics);
        }

        /// <summary>
        /// Dissociates a Graphic from a Canvas, removing this association from the registry.
        /// </summary>
        /// <param name="c">The Canvas to dissociate from the Graphic.</param>
        /// <param name="graphic">The Graphic to dissociate from the Canvas.</param>
        public static void UnregisterGraphicForCanvas(Canvas c, Graphic graphic)
        {
            if (c == null)
                return;

            IndexedSet<Graphic> graphics;
            if (instance.m_Graphics.TryGetValue(c, out graphics))
            {
                graphics.Remove(graphic);

                if (graphics.Count == 0)
                    instance.m_Graphics.Remove(c);
            }
        }

        private static readonly List<Graphic> s_EmptyList = new List<Graphic>();

        /// <summary>
        /// Retrieves the list of Graphics associated with a Canvas.
        /// </summary>
        /// <param name="canvas">The Canvas to search</param>
        /// <returns>Returns a list of Graphics. Returns an empty list if no Graphics are associated with the specified Canvas.</returns>
        public static IList<Graphic> GetGraphicsForCanvas(Canvas canvas)
        {
            IndexedSet<Graphic> graphics;
            if (instance.m_Graphics.TryGetValue(canvas, out graphics))
                return graphics;

            return s_EmptyList;
        }
    }
}

上一篇下一篇

猜你喜欢

热点阅读