源碼位置
- 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)。

類/接口
如何找到一個接口或者類?
因為 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 一樣的。下面是源文件,中文注釋是自己寫的。
*OnRectTransformDimensionsChange,OnBeforeTransformParentChanged ,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()前。

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 腳本的值來測試。


總結(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;
}
}
}









