UGUI 源碼精讀1 (UIBehaviour)

源碼位置

  • 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 里面。

目錄結(jié)構(gòu)

可以新建一個項目,然后在 Packages/Unity UI 里面看到目錄結(jié)構(gòu)。


Unity UI 的目錄結(jié)構(gòu)

類/接口

如何找到一個接口或者類?

因為 UGUI 的源文件是在Package 里面,所以在 VS 里面打開之后都是雜項文件,那么對于我們尋找類與類之間的關(guān)系就很不方便。
比如對于 Graphic (定義如下) 而言,想找到 ICanvasElement 所在的文件,然后會發(fā)現(xiàn)并不能通過按住 Ctrl 再點擊鼠標來定位到該接口聲明的位置,而且也沒有名為 ICanvasElement 的文件名

public abstract class Graphic
        : UIBehaviour,
          ICanvasElement
  • 方法一:可以在文件管理器中使用 findStr /s "ICanvasElement" *.* 來遞歸查找所有出現(xiàn)過 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",那么目錄結(jié)構(gòu)為:
    • |——Assert
      |——PackageSource
      |????|——com.unity.ugui

    • 更改 Packages 下面的 manifest.json 文件,將 "com.unity.ugui": "1.0.0", 變?yōu)?"com.unity.ugui": "file:C:/Files/Unity/Projects/UGUITest/PackageSource/com.unity.ugui",

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

    • 完成后的目錄結(jié)構(gòu)

TODO
第三種方法會導致 create 里面沒有 UI 這一欄。

參考
Unity打開Package目錄下cs代碼顯示雜項文件的解決辦法

UIBehaviour

位置:RunTime\EventSystem\UIBehaviour.cs
里面有很多虛函數(shù),Unity Monobehaviour 里面可以看到,比如 OnRectTransformDimensionsChange 這種不是很熟悉的,其實是跟 Start、Update 一樣的。下面是源文件,中文注釋是自己寫的。

*OnRectTransformDimensionsChangeOnBeforeTransformParentChanged ,OnDidApplyAnimationProperties 是在 Unity Monobehaviour 里面找不到的,合理懷疑是官方文檔沒有更新,因為 OnBeforeTransformParentChanged() 上面說看 MonoBehaviour.OnBeforeTransformParentChanged ,但 Unity MonoBehaviour 里面并沒有這個函數(shù)

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

   // 抽象類,雖然里面沒有抽象函數(shù),但是申明為抽象類可以防止被實例化
    public abstract class UIBehaviour : MonoBehaviour
    {
        // 虛函數(shù),可以被重寫,也可以不被重寫,相較于抽象函數(shù),沒有強制性(抽象函數(shù)子類必須要實現(xiàn))
        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 里面沒有這個函數(shù),所以是從 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;
        }
    }
}

現(xiàn)在做一個小實驗,也就是在每個函數(shù)里面加上 Debug.log("函數(shù)名") ,可以看出各個虛函數(shù)都是什么時候調(diào)用的。

OnValidate()

介紹:當腳本被加載或者 Inspector 面板的值出現(xiàn)變化的時候會被調(diào)用,你可以通過它來保證 Inspector 上的一個值固定在一個范圍之內(nèi),并且這個回調(diào)函數(shù)只有在編輯器模式下在會被調(diào)用,所以使用的時候最好用 #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

現(xiàn)象:

  • 不管是在編輯模式還是運行模式,啟用或者取消啟用本腳本,都會被調(diào)用。
  • 在 Inspector 中修改任意本組件中的值都會被調(diào)用


    OnValidate.gif

Reset()

介紹:將腳本恢復(fù)為默認值。同樣的,使用的時候最好用 #if UNITY_EDITOR,#endif 包裹住。

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

現(xiàn)象:

  • Inspector 面板,每個腳本右上角的那三個點,有個 reset,點它的時候會被調(diào)用 。
  • 只有編輯模式可以,運行時不可以


    Reset.gif

OnRectTransformDimensionsChange()

介紹:當 RectTransform 發(fā)生改變的時候被調(diào)用。

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

現(xiàn)象:

  • 在編輯模式下不會被調(diào)用
  • 運行模式下,更改 Width,Height,Left,Top,Anchors,Pivot 會被調(diào)用
  • 修改 Pos X/Y/Z,Rotation,Scale 不會被調(diào)用


    OnRectTransformDimensionsChange 編輯模式

    OnRectTransformDimensionsChange 運行模式

OnBeforeTransformParentChanged() & OnTransformParentChanged()

介紹:當腳本所在的物體的的父物體發(fā)生改變,或者父物體的父物體發(fā)生改變時會被調(diào)用。OnBeforeTransformParentChanged() 發(fā)生在 OnTransformParentChanged() 前??梢岳斫鉃?Update()LateUpdate() 的關(guān)系。

    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");
    }
  • 現(xiàn)象1:編輯狀態(tài)下不會被調(diào)用
  • 現(xiàn)象2:當父物體或者父物體的父物體,及以上發(fā)生改變的時候會被調(diào)用,但子物體以下發(fā)生改變不會被調(diào)用。(改變是指層級關(guān)系,不是 Inspector 的 Transform 組件內(nèi)容發(fā)生改變)
  • 現(xiàn)象3:OnBeforeTransformParentChanged() 永遠發(fā)生在 OnTransformParentChanged() 前。
OnTransformParentChanged.gif

OnCanvasHierarchyChanged()

介紹:當 Canvas 的可用性發(fā)生改變的時候調(diào)用。(官方介紹說是父物體的 Canvas 發(fā)生變化,但是經(jīng)實驗發(fā)現(xiàn)即使該腳本和 Canvas 在同一個物體上也會被調(diào)用。)

 /// <summary>
    /// Called when the state of the parent Canvas is changed.
    /// </summary>
    protected virtual void OnCanvasHierarchyChanged()
    {
        Debug.Log("OnCanvasHierarchyChanged");
    }
  • 現(xiàn)象1:編輯模式下不會被調(diào)用
  • 現(xiàn)象2:當 自己,父物體,或者父物體的父物體上的 Canvas 組件的可用性發(fā)生改變的時候會被調(diào)用,子物體身上的 Canvas 發(fā)生變化時不會被調(diào)用。
  • 現(xiàn)象3:Canvas 上的 有些值 發(fā)生改變的時候不會被調(diào)用,可用性發(fā)生變化時一定會被調(diào)用(TODO:有些值可能是跟可用性有關(guān)的)
  • 現(xiàn)象4:當把測試腳本的 勾勾去掉 的時候,該函數(shù)依然會在合適的時候被調(diào)用。
    現(xiàn)象1

    現(xiàn)象2

    現(xiàn)象3

    現(xiàn)象4

OnDidApplyAnimationProperties()

介紹:當物體的屬性被 Animation 修改的時候會被調(diào)用。

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

現(xiàn)象:

  • 當沒有 Update() 的時候(也就是不會修改物體的某項屬性),通過 Animation 修改 MyInt,不會被調(diào)用。
  • 當有 Update() 的時候(也就是會修改物體的某項屬性),通過 Animation 修改 MyInt,該函數(shù)會被調(diào)用。
  • 必須要用動畫去修改當前腳本上的值,并且該值會影響到物體才可以,比如直接用動畫修改 Transform,是不會被調(diào)用的。
  • 編輯模式下在適當?shù)臅r候也會被調(diào)用。這一點可以通過繼承自 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 腳本的值來測試。


現(xiàn)象1,2
現(xiàn)象2,3

總結(jié):LayoutGroup.OnDidApplyAnimationProperties 雖然也有實現(xiàn)這個函數(shù),但認為它依然是通過 MonoBehavior 來進行回調(diào)的,就像 Start() 一樣。

OnCanvasGroupChanged()

介紹:父物體或者自身的 Canvas Group 組件發(fā)生變化(包括值改變或者可用性改變)時被調(diào)用。

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

現(xiàn)象:

  • 在編輯模式下依然可以被調(diào)用。
  • 父物體或者自身的 Canvas Group 組件發(fā)生變化(包括值改變或者可用性改變)時被調(diào)用。
  • 當子物體身上的 Canvas Group 組件發(fā)生變化的時候,不會被調(diào)用。


    OnCanvasGroupChanged.gif

結(jié)論

上面這些方法的實驗,基本上都是直接或者間接繼承了 MonoBehaviour 來完成的,所以我也猜測不管在官方的 MonoBehaviour 里面有沒有出現(xiàn)某一個方法,它都是由 MonoBehaviour 來調(diào)用的,具體是如何被調(diào)用的,可以參考 Unity3d是如何調(diào)用MonoBehaviour子類中的Start等方法的?

參考(致謝)

UGUI系統(tǒng)研究講解-----》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;
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容