美文网首页
UGUI 源码精读1 (UIBehaviour)

UGUI 源码精读1 (UIBehaviour)

作者: 烂醉花间dlitf | 来源:发表于2020-10-11 16:37 被阅读0次

    源码位置

    • Unity 2019.2 之前在 https://github.com/Unity-Technologies/uGUI 里面
    • Unity 2019.2 及以后在 Unity 的安装位置,比如我的就在 D:\Unitys\2019.4.11f1\Editor\Data\Resources\PackageManager\BuiltInPackages\com.unity.ugui 里面。

    目录结构

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


    Unity UI 的目录结构

    类/接口

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

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

    public abstract class Graphic
            : UIBehaviour,
              ICanvasElement
    
    • 方法一:可以在文件管理器中使用 findStr /s "ICanvasElement" *.* 来递归查找所有出现过 ICanvasElement 的语句。
    • 方法二:在 VS 2017 里面可以直接将 D:\Unitys\2019.4.11f1\Editor\Data\Resources\PackageManager\BuiltInPackages\com.unity.ugui 拖拽到 VS 的程序集中,就可以正常索引了,但这种方法在 VS 2019 里面不可行。所以 2019 可以参考方法三。
    • 方法三:先将 D:\Unitys\2019.4.11f1\Editor\Data\Resources\PackageManager\BuiltInPackages\com.unity.ugui 放到一个和 Assert 同级的目录,比如取名为 "PackageSource",那么目录结构为:
      • |——Assert
        |——PackageSource
        |    |——com.unity.ugui

      • 更改 Packages 下面的 manifest.json 文件,将 "com.unity.ugui": "1.0.0", 变为 "com.unity.ugui": "file:C:/Files/Unity/Projects/UGUITest/PackageSource/com.unity.ugui",

      • 等待 Unity 和 VS 重新加载就可以在 VS 中看到 UGUI 的代码了

      • 完成后的目录结构

    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
    

    现象:

    • 不管是在编辑模式还是运行模式,启用或者取消启用本脚本,都会被调用。
    • 在 Inspector 中修改任意本组件中的值都会被调用


      OnValidate.gif

    Reset()

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

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

    现象:

    • Inspector 面板,每个脚本右上角的那三个点,有个 reset,点它的时候会被调用 。
    • 只有编辑模式可以,运行时不可以


      Reset.gif

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

    现象:

    • 在编辑模式下不会被调用
    • 运行模式下,更改 Width,Height,Left,Top,Anchors,Pivot 会被调用
    • 修改 Pos X/Y/Z,Rotation,Scale 不会被调用


      OnRectTransformDimensionsChange 编辑模式
      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");
        }
    
    • 现象1:编辑状态下不会被调用
    • 现象2:当父物体或者父物体的父物体,及以上发生改变的时候会被调用,但子物体以下发生改变不会被调用。(改变是指层级关系,不是 Inspector 的 Transform 组件内容发生改变)
    • 现象3:OnBeforeTransformParentChanged() 永远发生在 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");
        }
    
    • 现象1:编辑模式下不会被调用
    • 现象2:当 自己,父物体,或者父物体的父物体上的 Canvas 组件的可用性发生改变的时候会被调用,子物体身上的 Canvas 发生变化时不会被调用。
    • 现象3:Canvas 上的 有些值 发生改变的时候不会被调用,可用性发生变化时一定会被调用(TODO:有些值可能是跟可用性有关的)
    • 现象4:当把测试脚本的 勾勾去掉 的时候,该函数依然会在合适的时候被调用。
      现象1
      现象2
      现象3
      现象4

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

    现象:

    • 当没有 Update() 的时候(也就是不会修改物体的某项属性),通过 Animation 修改 MyInt,不会被调用。
    • 当有 Update() 的时候(也就是会修改物体的某项属性),通过 Animation 修改 MyInt,该函数会被调用。
    • 必须要用动画去修改当前脚本上的值,并且该值会影响到物体才可以,比如直接用动画修改 Transform,是不会被调用的。
    • 编辑模式下在适当的时候也会被调用。这一点可以通过继承自 GridLayoutGroup 然后将代码改为
    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");
    }
    

    现象:

    • 在编辑模式下依然可以被调用。
    • 父物体或者自身的 Canvas Group 组件发生变化(包括值改变或者可用性改变)时被调用。
    • 当子物体身上的 Canvas Group 组件发生变化的时候,不会被调用。


      OnCanvasGroupChanged.gif

    结论

    上面这些方法的实验,基本上都是直接或者间接继承了 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;
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:UGUI 源码精读1 (UIBehaviour)

          本文链接:https://www.haomeiwen.com/subject/ckopuktx.html