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

目录结构

可以新建一个项目,然后在 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;
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343