精华内容
下载资源
问答
  • UGUI源码
    千次阅读
    2019-04-26 10:28:59

    1.下载UGUI源码
    unity在bitbucket上开源了各个unity版本的UI源码,地址是:https://bitbucket.org/Unity-Technologies/ui/downloads?tab=tags

    UnityEditor.UI文件夹是UGUI的编辑器实现类,因为Unity中规定编辑器实现类必须以Editor命名,所以要将UnityEditor.UI改名为Editor。

    更多相关内容
  • Unity3d UGUI源码下载

    2018-07-31 17:43:42
    UGUI源码,通过研究UGUI源码。获得更多的UI修改灵感。
  • UGUI源码-2020.10.07.zip

    2021-03-10 19:55:52
    目前官方提供UGUI源码最新版本为2020.10.07,之后假如官方提供的源码更新,会同步更新并修改描述里的源码日期
  • UNITY2017.3 自带UI系统 UGUI源码, 可以替换本地安装中的unityengine.dll文件
  • UGUI 源码~~

    2018-09-04 00:10:14
    UGUI 源码~
  • UGUI 源码大家一起看

    2018-07-16 17:28:29
    不是吹的我真的能写到50字节,Ugui源码大家一起看看便宜实惠不要错过
  • unity-UGUI源码分享

    2019-08-23 15:27:43
    UNITY最新版本UGUI源码分享,想了解UI原理的同学有福了。
  • Unity UGUI源码解析

    千次阅读 多人点赞 2021-07-18 21:32:56
    这篇文章想写的目的也是因为我面试遇到了面试官问我关于UGUI原理性的问题,虽然我看过,但是并没有整理出完整的知识框架,导致描述的时候可能自己都是不清不楚的。自己说不清楚的东西,别人就等于你不会。每当学完一...

    前言

    这篇文章想写的目的也是因为我面试遇到了面试官问我关于UGUI原理性的问题,虽然我看过,但是并没有整理出完整的知识框架,导致描述的时候可能自己都是不清不楚的。自己说不清楚的东西,别人就等于你不会。每当学完一个东西的时候,应该会大体框架流程,具体实现细节有所了解,然后整理出来,以备日后查阅。人的记忆是有限的,如果不记下来,每次翻的都是别人的博客,这样其实是一个很不好的习惯。所以决定整理出一些自己关于UGUI的了解。以上只是我的牢骚,下面开始今天的内容。


    学习方法论与要点整理

    在框架之前我们需要先思考,我们要从哪里入手来看这个框架。引用我最近经常听到的一句话吧,你写的这个东西有什么用呢,他解决了什么问题。这确实是需要值得思考的问题,我们为什么要用UGUI,他人给我们提供了什么东西,为我们解决了什么问题。那么从这个思路开始,我们来梳理这个框架。

    学习方法论

    我们要看一个框架,会发现这个框架的代码实在是太多了,不同于我们我们平时写模块化的逻辑,点开一个脚本基本上只能看到很小部分框架业务逻辑实现,那么下面就推荐我看代码的思路。

    • 用全宇宙最强编辑器生成UML图,通过观看框架结构接口定义,来实现对整个框架结构有个概览。
    • 下载源码,尝试写代码对单个功能点的逻辑进行断点调试,跟着断点查看整个框架代码的执行流程

    举个例子,假如你需要看Image的绘制流程,只需要写个测试代码,对Image的color进行赋值,会触发Canvas刷新,对Image进行更新,这时候就可以跟着断点走进去看到整个逻辑执行顺序

    UGUI功能点

    让我们分析一个UI界面所需要的要点,抛开业务逻辑,那么一个UI框架需要为我们提供的是

    • UI要素的渲染
    • 可控的渲染层级排序
    • UI布局的自适应
    • 交互事件的检测与响应
    • 面向功能性的UI组件

    UGUI框架解析

    UGUI框架结构概览

    框架UML图如下, 点击试试看能不能放大
    在这里插入图片描述
    UGUI框架结构几个核心的类如下:

    • UIBehaviour 抽象类 继承MonoBehaviour,提供核心事件驱动
    • Graphic 抽象类 提供绘制接口,各种Dirty方法来注册到更新列队
    • EventSystem 事件中心,负责处理各种输入,光线投射,以及发送事件
    • BaseInputModule 输入模块基类,负责发送各种输入事件到GameObject
    • BaseRaycaster 光线投射基类
    • EventInterfaces 注意这是一个脚本,他定义了所有EventSystem事件方法接口

    其他功能性的组件,以及布局我就不一一列举了,具体请查看源码


    渲染以及重绘流程

    在unity中我们绘制一个图形需要几个基本要素,Mesh,Material,Texture,以下的几个要素都被定义在了Graphic中。这些属性都是被CanvasRenderer这个组件托管进行渲染的,我们可以再编写组件的时候对这些要素进行定义和替换,但是最后都需要赋值到CanvasRenderer中由Canvas进行渲染。

    网格绘制

    在unity中我们绘制一个图形需要几个基本要素,Mesh,Material,Texture,以下的几个要素都被定义在了Graphic中,那么首先让我们看一下UGUI源码的例子,看他是怎么对一个网格进行绘制的

    using UnityEngine;
    using UnityEngine.UI;
    [ExecuteInEditMode]
    // 这是一个简易的基于UGUI的Image实现
    public class SimpleImage : Graphic
    {
        // 这里是 Graphic 可重写的绘制网格的接口
        protected override void OnPopulateMesh(VertexHelper vh)
        {
            // VertexHelper 这是UGUI一个用于构建网格的帮助类
            // 他可以用于填充顶点数据,设置三角面片信息,并缓存他们
            // 最后用这些数据填充并构建一个mesh
    
            // 构建顶点数据
            // 此处两个Vector2结构体的数据来源为当前Gameobject的Rectransform
            // 因为RectTransform的数据是基于布局自适应的
            Vector2 corner1 = Vector2.zero;
            Vector2 corner2 = Vector2.zero;
            corner1.x = 0f;
            corner1.y = 0f;
            corner2.x = 1f;
            corner2.y = 1f;
            corner1.x -= rectTransform.pivot.x;
            corner1.y -= rectTransform.pivot.y;
            corner2.x -= rectTransform.pivot.x;
            corner2.y -= rectTransform.pivot.y;
            corner1.x *= rectTransform.rect.width;
            corner1.y *= rectTransform.rect.height;
            corner2.x *= rectTransform.rect.width;
            corner2.y *= rectTransform.rect.height;
            vh.Clear();
            UIVertex vert = UIVertex.simpleVert;
            vert.position = new Vector2(corner1.x, corner1.y);
            vert.color = color;
            vh.AddVert(vert);
            vert.position = new Vector2(corner1.x, corner2.y);
            vert.color = color;
            vh.AddVert(vert);
            vert.position = new Vector2(corner2.x, corner2.y);
            vert.color = color;
            vh.AddVert(vert);
            vert.position = new Vector2(corner2.x, corner1.y);
            vert.color = color;
            vh.AddVert(vert);
    
            // 此处指定了三角面片的绘制顺序 顺时针方向
            // 0 (0,0) 顶点在数组中的index(x坐标,y坐标)
            //
            // 1 (0,1)   >    2 (1,1) 
            //    ...........
            //    .       . .
            // ^  .    .    .
            //    . .       .
            //    ...........
            // 0 (0,0)  <     3 (1,1)
            vh.AddTriangle(0, 1, 2);
            vh.AddTriangle(2, 3, 0);
        }
    }
    

    通过以上的例子应该对网格绘制原理有所了解,并且也能通过官方例子来实现一个简易的Image绘制。

    材质

    在UGUI的Image中,就算我们没有给他其特定的材质,Image还是能够进行正常的渲染。Unity为我们设置了一个默认的材质定义,他的位置在Sahder面板中的UI/Default。以下静态方法被定义在Canvas组件中

        public static Material GetDefaultCanvasMaterial();
        public static Material GetDefaultCanvasTextMaterial();
        public static Material GetETC1SupportedCanvasMaterial();
    
    重绘流程

    由于Canvas没有开源代码,没办法看到底层的渲染流程,只能通过调整参数来控制他的渲染逻辑,下面讲一下主要的几个控制参数。

    RenderMode

    • ScreenSpace-Overlay 屏幕空间并覆盖在屏幕上,也就是说,当前渲染层级永远在最上层
    • ScreenSpace-Camera 屏幕空间并由Camera来控制渲染,这个多了一层Camera套娃,可以来控制渲染和层级
    • WorldSpace 世界空间,这个渲染是三维空间的,可以用z轴进行渲染排序

    PixelPerfect 强制于像素对齐,无特殊需要建议关闭,会影响性能

    SortOrder 在ScreenSpace-Overlay模式下用于控制Canvas的渲染顺序

    SortingLayer 世界空间下画布的渲染层

    OrderInLayer 画布在当前渲染层下的渲染顺序

    AdditionalShaderChannels 顶点数据的遮罩

    那么下面我们再来说一下UGUI的重绘流程,在UGUI中渲染的核心就是Canvas组件和CanvasRenderer组件,Graphic只是为了填充渲染的必要元素,他并不直接参与绘制,那么让我们看一下他是怎么触发整个Canvas重新绘制的,这是Canvas中重绘的关键事件

        public static event WillRenderCanvases willRenderCanvases;
    

    那么让我们思考一下,什么情况下需要触发重新绘制,就是当前Canvas下需要绘制的物体属性变更的时候,一共有以下几个点。

    • 材质变更
    • 贴图变更
    • Mesh信息变更
    • 位置变更
    • 显示与隐藏

    让我们看一下UGUI源码这个简单的例子。

        // 设置顶点颜色
        public virtual Color color 
        { 
            get 
            { 
                return m_Color; 
            } 
            set 
            { 
                if (SetPropertyUtility.SetColor(ref m_Color, value)) 
                SetVerticesDirty(); 
            } 
        }
    
        // 设置材质
        public virtual Material material
        {
            get
            {
                return (m_Material != null) ? m_Material : defaultMaterial;
            }
            set
            {
                if (m_Material == value)
                    return;
    
                m_Material = value;
                SetMaterialDirty();
            }
        }
    
        // 材质变化 将自身注册到重绘列表中
        public virtual void SetMaterialDirty()
        {
            if (!IsActive())
                return;
    
            m_MaterialDirty = true;
            CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
    
            if (m_OnDirtyMaterialCallback != null)
                m_OnDirtyMaterialCallback();
        }
    
        // 顶点变化 将自身注册到重绘列表中
        public virtual void SetVerticesDirty()
        {
            if (!IsActive())
                return;
    
            m_VertsDirty = true;
            CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
    
            if (m_OnDirtyVertsCallback != null)
                m_OnDirtyVertsCallback();
        }
    

    以上的方法都是在Graphic类中,一个用于一个顶点颜色,也就是顶点信息改变,一个用于设置材质,也就是材质改变,相对应的每个需要渲染的属性都有其属性设置器,我们可以看到他在set的时候调用了一个相对应的Dirty的方法,他会在方法中把自身注册进CanvasUpdateRegistry这个类中,这个类是重绘的关键,让我们看一下这个类主要实现了什么逻辑,以下列举几个关键方法

        public class CanvasUpdateRegistry
        {
            // 构造方法,将PerformUpdate方法注册进Canvas即将渲染前会调用的事件
            protected CanvasUpdateRegistry()
            {
                Canvas.willRenderCanvases += PerformUpdate;
            }
    
            // 将组件注册进布局重建的列表
            public static void RegisterCanvasElementForLayoutRebuild(ICanvasElement element)
            {
                instance.InternalRegisterCanvasElementForLayoutRebuild(element);
            }
    
            // 将组件注册进渲染重建的列表
            public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element)
            {
                instance.InternalRegisterCanvasElementForGraphicRebuild(element);
            }
    
            // 更新布局与渲染的方法
            private void PerformUpdate()
            {
                // 由于篇幅限制这里只贴了更新Graphic的部分,实际上还有布局更新的部分,请自行查阅源码
                var graphicRebuildQueueCount = m_GraphicRebuildQueue.Count;
                for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
                {
                    UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);
                    for (var k = 0; k < graphicRebuildQueueCount; k++)
                    {
                        try
                        {
                            var element = m_GraphicRebuildQueue[k];
                            if (ObjectValidForUpdate(element))
                            //这个是关键方法,他根据Canvas更新的流程来选择不同的绘制方法,
                            // 来重建Graphic
                                element.Rebuild((CanvasUpdate)i);
                        }
                        catch (Exception e)
                        {
                            Debug.LogException(e, m_GraphicRebuildQueue[k].transform);
                        }
                    }
                    UnityEngine.Profiling.Profiler.EndSample();
                }
    
                for (int i = 0; i < graphicRebuildQueueCount; ++i)
                    m_GraphicRebuildQueue[i].GraphicUpdateComplete();
    
                m_GraphicRebuildQueue.Clear();
                m_PerformingGraphicUpdate = false;
                UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Render);
            }
        }
    

    Tips:先描述一下 ICanvasElement 这个类的定义,这个类是顾名思义他就是Canvas元素,而Canvas主要负责绘制,所以他是一个所有需要带绘制属性组件的基类,这个类中定义了 Rebuild 方法,可以在这个方法对主要元素进行重新绘制,顺便贴上在 Graphic 中的ReBuild实现

        // 核心的重绘方法
        public virtual void Rebuild(CanvasUpdate update)
        {
            if (canvasRenderer == null || canvasRenderer.cull)
                return;
    
            // 根据自定义的更新流程来更新顶点和材质
            switch (update)
            {
                case CanvasUpdate.PreRender:
                    if (m_VertsDirty)
                    {
                        UpdateGeometry();
                        m_VertsDirty = false;
                    }
                    if (m_MaterialDirty)
                    {
                        UpdateMaterial();
                        m_MaterialDirty = false;
                    }
                    break;
            }
        }
    

    接着上面说,我们可以看到这个类主要做了这几件事情,在构造的时候将更新方法注册进 Canvas.willRenderCanvases 中,这个事件是会在Canvas更新前被调用,然后可以利用 RegisterCanvasElementForGraphicRebuild 这个方法将需要绘制的元素注册进待更新的队列,然后在Canvas即将渲染时会执行这个类的更新方法 PerformUpdate,这时候他会根据自定义的CanvasUpdate更新流程作为参数,调用每个element的Rebuild函数

    总结:我们可以看上片段逻辑已经实现了一个绘制的基本要素和触发重绘的条件,形成了一个完整的闭环,
    核心就是依赖的Canvas即将要渲染前的事件,来构造这样一段逻辑,主要是实现了观察者模式,算是一个比较高效率的实现,所以在我们做项目的UGUI优化的时候也是主要观察这个willRenderCanvases,被注册的次数,尽量用Canvas来隔开不必要的重绘次数,当然这需要和Drawcall之间进行衡量,找出性能的瓶颈点来进行优化。

    事件触发以及响应

    在看之前首先让我们思考,如果让我们编写一个事件系统的话,我们会怎么实现这样一个功能。
    很明显,事件就是一个观察者模式,他有一个事件中心(EventSystem),以及众多的观察者(例: Button),当然我们也需要对事件进行类型区分,比如说 OnClick, OnDrag,很明显他们实现的功能职责不同,既然我们要区别这些事件类型,那么我们必然需要一个用户输入的手势数据(例:PointerEventData)用来判断区分不同的事件触发,既然已经整理清楚思路了,就让我们看一下UGUI中模块的定义。

    模块定义

    在这里插入图片描述

    他主要根据文件目录分了以下几个模块,输入数据模块(EventData), 输入模块(InputModules), 射线模块(Raycaster), UI元素模块(UIElement)

    事件绑定

    让我们先看一段我们平时在代码中会编写的方法触发逻辑,这里主要利用了Button。

        void Start()
        {
            _button = GetComponent<Button>();
            _button.onClick.AddListener(func);
        }
    

    这个方法主要是把 func 托管给了Button这个组件,然后来进行事件的触发,那么让我们看看Button里面做了什么把。

        public class Button : Selectable, IPointerClickHandler, ISubmitHandler
        {
            public class ButtonClickedEvent : UnityEvent {}
    
            private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();
    
            // 方法绑定在这个事件中
            public ButtonClickedEvent onClick
            {
                get { return m_OnClick; }
                set { m_OnClick = value; }
            }
    
            private void Press()
            {
                if (!IsActive() || !IsInteractable())
                    return;
    
                UISystemProfilerApi.AddMarker("Button.onClick", this);
                m_OnClick.Invoke();
            }
    
            // 这里是主要的触发逻辑,实现了 IPointerClickHandler 接口
            public virtual void OnPointerClick(PointerEventData eventData)
            {
                if (eventData.button != PointerEventData.InputButton.Left)
                    return;
    
                Press();
            }
        }
    
        // Click事件的接口
        public interface IPointerClickHandler : IEventSystemHandler
        {
            void OnPointerClick(PointerEventData eventData);
        }
    

    我们可以发现组件想要触发事件主要依赖于实现UGUI的接口,然后框架会调用接口中的方法来触发组件的逻辑
    能触发这种不同类型的事件的接口有很多,他们都被定义在了 EventInterfaces 这个脚本中,所有的接口都继承了 IEventSystemHandler 这个事件触发的基类。

    事件检测以及触发

    我们要触发事件,必然需要一个核心驱动来获取我们每帧的手势输入,我们先看一下事件触发的核心驱动部分逻辑。

        // 这是挂载在场景中的核心驱动模块
        public class EventSystem : UIBehaviour
        {
            // 输入模块
            private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();
            // 当前的输入模块
            private BaseInputModule m_CurrentInputModule;
            // 事件中心列表,实际上当前场景只允许有一个
            private  static List<EventSystem> m_EventSystems = new List<EventSystem>();
    
            // 每帧驱动输入模块的逻辑
            protected virtual void Update()
            {
                // 核心逻辑,截取部分,具体的查阅源码
                if (current != this)
                    return;
                // 更新驱动输入模块
                TickModules();
    
                if (!changedModule && m_CurrentInputModule != null)
                    // 这个模块方法里面会构造当前的输入数据
                    m_CurrentInputModule.Process();
    
                // 可以看到驱动执行事件在处理输入数据之前,这说明我们的输入数据会在这一帧被构造,在下一帧被执行
            }
    
            // 更新驱动输入模块
            private void TickModules()
            {
                var systemInputModulesCount = m_SystemInputModules.Count;
                for (var i = 0; i < systemInputModulesCount; i++)
                {
                    if (m_SystemInputModules[i] != null)
                        // 这里驱动对应的输入模块了
                        m_SystemInputModules[i].UpdateModule();
                }
            }
        }
    

    以上逻辑可以看到EventSystem模块主要负责和驱动相应的输入模块,具体实现要延迟到各个输入模块中,要注意的点是,输入数据会在这一帧被构造,在下一帧被执行, 那么我们以 TouchInputModule 为例,来看看他实现了什么功能。

        // 这是一个触摸的输入模块
        public class TouchInputModule : PointerInputModule
        {
            // 定义了输入的数据
            private PointerEventData m_InputPointerEvent;
    
            // 每帧更新的方法,执行当前的输入事件
            public override void UpdateModule()
            {
                if (!eventSystem.isFocused)
                {
                    if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
                        // 这是核心逻辑,这里执行了对应的数据的事件,
                        ExecuteEvents.Execute(m_InputPointerEvent.pointerDrag, m_InputPointerEvent, ExecuteEvents.endDragHandler);
    
                    m_InputPointerEvent = null;
                }
    
                m_LastMousePosition = m_MousePosition;
                m_MousePosition = input.mousePosition;
            }
    
            // 构造当前的输入数据
            public override void Process()
            {
                if (UseFakeInput())
                    FakeTouches();
                else
                    ProcessTouchEvents();
            }
    
            // 构造当前的输入数据
            private void ProcessTouchEvents()
            {
                for (int i = 0; i < input.touchCount; ++i)
                {
                    // 这里终于能看见调用Unity的API来通过GetTouch来获取当前的触摸点
                    Touch touch = input.GetTouch(i);
    
                    if (touch.type == TouchType.Indirect)
                        continue;
    
                    bool released;
                    bool pressed;
                    var pointer = GetTouchPointerEventData(touch, out pressed, out released);
    
                    // 在这个方法里面会对当前的 m_InputPointerEvent 进行赋值
                    // 这样当前帧的输入信息的构造好了
                    ProcessTouchPress(pointer, pressed, released);
    
                    if (!released)
                    {
                        ProcessMove(pointer);
                        ProcessDrag(pointer);
                    }
                    else
                        RemovePointerData(pointer);
                }
            }
        }
    

    我们可以看到这个输入模块每帧构造当前的输入数据以及每帧驱动模块进行事件的分发,并且他调用了核心的方法 ExecuteEvents.Execute(),这个方法会负责触发对应接口的方法,让我们看一下这个方法的定义

        // 这里把需要执行的方法定义在了外部
        public delegate void EventFunction<T1>(T1 handler, BaseEventData eventData);
    
        private static readonly EventFunction<IPointerEnterHandler> s_PointerEnterHandler = Execute;
    
        private static void Execute(IPointerEnterHandler handler, BaseEventData eventData)
        {
            handler.OnPointerEnter(ValidateEventData<PointerEventData>(eventData));
        }
    
        public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
        {
            // 这个方法主要实现了收集gameObject上所有的 IEventSystemHandler 组件
            var internalHandlers = ListPool<IEventSystemHandler>.Get();
            GetEventList<T>(target, internalHandlers);
    
            var internalHandlersCount = internalHandlers.Count;
            for (var i = 0; i < internalHandlersCount; i++)
            {
                T arg;
                try
                {
                    arg = (T)internalHandlers[i];
                }
                catch (Exception e)
                {
                    var temp = internalHandlers[i];
                    Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e));
                    continue;
                }
    
                try
                {
                    // 执行对应的方法以及传入数据参数
                    functor(arg, eventData);
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
            }
    
            var handlerCount = internalHandlers.Count;
            ListPool<IEventSystemHandler>.Release(internalHandlers);
            return handlerCount > 0;
        }
    
        // 这个方法主要实现了收集gameObject上所有的 IEventSystemHandler 组件
        private static void GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
        {
            // Debug.LogWarning("GetEventList<" + typeof(T).Name + ">");
            if (results == null)
                throw new ArgumentException("Results array is null", "results");
    
            if (go == null || !go.activeInHierarchy)
                return;
    
            var components = ListPool<Component>.Get();
            go.GetComponents(components);
    
            var componentsCount = components.Count;
            for (var i = 0; i < componentsCount; i++)
            {
                if (!ShouldSendToComponent<T>(components[i]))
                    continue;
    
                // Debug.Log(string.Format("{2} found! On {0}.{1}", go, s_GetComponentsScratch[i].GetType(), typeof(T)));
                results.Add(components[i] as IEventSystemHandler);
            }
            ListPool<Component>.Release(components);
            // Debug.LogWarning("end GetEventList<" + typeof(T).Name + ">");
        }
    

    主要需要分析的是Execute这个方法的 functor 参数,他实现的功能就是相对应需要执行的方法如OnPointerEnter,定义在了外部,通过传参的方法来执行对应的方法,

    参数还有一个gameObject,这是一个必须的参数,这个gameObject在构建输入数据的时候已经被赋值,并随着输入数据进行传递,让我们来看一下UGUI是怎么获取当前需要接受这个事件的gameObject的。

        // 所有输出模块的基类
        public abstract class BaseInputModule : UIBehaviour
        {
            // 接受事件检测的对象列表
            protected List<RaycastResult> m_RaycastResultCache = new List<RaycastResult>();
        }
    
    
        public class EventSystem : UIBehaviour
        {
            // 这个方法调用了会获取所有需要检测的组件列表
            public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
            {
                raycastResults.Clear();
                var modules = RaycasterManager.GetRaycasters();
                var modulesCount = modules.Count;
                for (int i = 0; i < modulesCount; ++i)
                {
                    var module = modules[i];
                    if (module == null || !module.IsActive())
                        continue;
    
                    module.Raycast(eventData, raycastResults);
                }
    
                raycastResults.Sort(s_RaycastComparer);
            }
        }
    

    RaycastAll方法会获取所有需要响应检测的列表,并对其进行排序,然后在构造输入数据的时候获取第一个被检测的对象,这个对象就是那个GameObject,其他具体检测逻辑请参考Canvas的渲染模式以及源码

    对于Graphic对象来说,我们有两种方式控制其射线的检测逻辑,一种是继承并重写Graphic.Raycast方法,还有一种就是让子类同时继承 ICanvasRaycastFilter 接口, 实现其接口方法 IsRaycastLocationValid 来进行来实现过滤。


    总结

    UGUI的逻辑已经比较完善了,源框架代码也很规范,希望这篇文章可以帮助大家快速的通读UGUI的源码相关逻辑,共同进步,哦耶~

    好像少了一个部分还没说就是关于自适应布局逻辑及更新这块,还有蒙板逻辑,其他部分留着下次再补充吧

    布局组件这块已经更新好啦,>>>>>> 戳这里 Unity UGUI自适应布局系统详解<<<<<<

    那么今天就教程就到这里结束了,如果觉得我说的有用的话就在我的>>>>>> 戳这里 github项目<<<<<<点上一个小小的star吧,咖啡就不用请我喝了,屑屑(比心)

    展开全文
  • UGUI源码解析——总览

    千次阅读 2022-04-19 16:19:36
    ——UIBehaviour:UGUI源码解析——UIBehaviour UI组件的基类,负责接收一些事件 ——CanvasUpdateRegistry、ICanvasElement:UGUI源码解析——CanvasUpdateRegistry 监听画布的布局、网格、材质的更新 ——...

    一:图像相关


    ——UIBehaviour:UGUI源码解析——UIBehaviour
    UI组件的基类,继承MonoBehaviour
    ——CanvasUpdateRegistry、:UGUI源码解析——CanvasUpdateRegistry
    图像、布局重建注册器
    ——ICanvasElement:UGUI源码解析——CanvasUpdateRegistry
    图像、布局重建接口
    ——LayoutRebuilder:
    ——Graphic:UGUI源码解析——Graphic
    负责图像显示与更新的抽象类
    ——MaskableGraphic:
    ——IMaskable:
    遮罩处理接口
    ——ICliappable:
    裁剪相关处理接口
    ——IMaterialModifier:
    材质相关处理接口


    ——VertexHelper:UGUI源码解析——VertexHelper
    快速创建网格数据的工具类
    ——IMeshModifier:UGUI源码解析——IMeshModifier
    网格处理的接口,可以在子类中实现ModifyMesh方法去修改顶点数据
    ——BaseMeshEffect:UGUI源码解析——BaseMeshEffect
    用于实现网格效果的抽象基类,可以在子类中重写ModifyMesh方法去修改顶点数据,我们实现网格效果尽量继承此类(例如描边、阴影、镜像)
    ——Shadow:UGUI源码解析——Shadow
    阴影组件,实现原理是将原网格顶点复制出一份并向指定方向移动
    ——Outline:UGUI源码解析——Outline
    描边组件,实现原理是将原网格顶点复制出四份并向指定方向移动

    展开全文
  • Unity3d UGUI源码

    2017-12-16 09:43:24
    Unity3d官方的UGUI源码,验证可用,欢迎下载,谢谢!!
  • unity2017版本 ugui源码

    2020-10-13 20:07:32
    ugui源码UGUI源码中UnityEditor.UI、UnityEngine.UI两个文件夹删除两个文件夹里面的bin、obj和Properties文件夹,以及.csproj文件。这些都是不必要的内容,只保留源码
  • Unity中调试开源的UGUI源码

    千次阅读 2022-03-07 15:13:02
    Unity中调试开源的UGUI源码 最近有点颓废, 很久没写文章了, 为了激励自己, 准备开一个新坑: 结合官方文档, 我自己的使用体验, 还有我自己的理解等各个方面来对UGUI的源码进行简单的梳理和分析. 感觉这个系列我可以写...

    Unity中调试开源的UGUI源码

    最近有点颓废, 很久没写文章了, 为了激励自己, 准备开一个新坑: 结合官方文档, 我自己的使用体验, 还有我自己的理解等各个方面来对UGUI的源码进行简单的梳理和分析.

    感觉这个系列我可以写一年, 再也不担心找不到内容水文章了, 哈哈.

    看到网上很多人都做过这个尝试, 但是没写几篇文章就放弃了, 希望我自己能够尽可能坚持下去.

    好了, 废话不多说, 正式开始今天的内容.

    今天是整个系列的开篇, 主要介绍如何将官方开源的UGUI源码嵌入项目进行调试.

    环境搭建

    我们首先需要在官方的github上下载对应版本的UGUI源码, 因为官方只提供了部分版本的源码, 在查找的时候需要注意.

    出于研究和学习的目的, 为了不影响日常开发(后面的过程会对安装目录有修改), 我们可以找一个接近日常使用的版本, 比如我们日常使用2017.4.37f1, 在研究UGUI源码的时候, 我下载的版本是2017.3.1f1, 虽然版本不一样, 但是其实差异不大.

    源码下载好之后下载安装对应的Unity.

    之后进入目录unity安装目录:C:\Program Files\Unity\2017.3.1f1\Editor\Data\UnityExtensions\Unity, 找到GUISystem文件夹, 将其移动到安装目录之外(也可以备份后直接删除).

    GUISystem这个文件夹就是UGUI编译之后的库, 如果不移除, 后面就会冲突.

    在2019.2之后, 源码就以包的形式加入到安装目录, 不需要从github上下载了, 具体目参考为: C:\Program Files\Unity\2019.4.26f1\Unity\Editor\Data\Resources\PackageManager\BuiltInPackagesData\Resources\PackageManager\BuiltInPackages\com.unity.ugui.

    这里我们出于学习研究的目的, 所以直接使用2017即可.

    然后使用对应版本的unity新建一个项目, 如UnityUI, 此时进入editor后, UGUI相关的部分都无法使用, 我们将下载好的源码加入工程, 等编译完成即可使用.

    注意在加入源码的时候, 可以只加入UnityEditor.UIUnityEngine.UI, 加入后目录名字可以自行修改, 整个过程其实就是相当于我们自己写了一份代码挪进来用而已, 没什么复杂的东西.

    比如我就是将源码加入到Assets/Unity下:

    在这里插入图片描述

    还有就是如果保持原来的目录不变, 有时可能会报错, 所以最好能将目录改下名字, 比如我将UnityEditor.UI目录改为Editor, 如下:

    在这里插入图片描述

    到此, 我们整个环境就搭建好了. 大家可以使用平时使用的IDE, 如Rider, VS等对源码进行调试来观察和学习源码了.

    总结

    今天的内容不多, 但是却至关重要, 很多同学只靠看代码来研究源码, 效率可能不太高.

    相信按照今天的内容搭建好调试环境之后, 对于源码的学习和理解能够有提高极大的效率提升.

    下面几篇文章会围绕事件系统展开对UGUI源码的研究, 一步步深入, 尽量做到整清楚, 讲明白, 希望对大家有所帮助.

    展开全文
  • Unity UGUI 源码分析系列(完结)

    万次阅读 多人点赞 2020-04-19 18:05:03
    该系列注重分析UGUI源码,来深入了解UGUI每个模块每个组件的实现原理,使我们对UGUI使用和拓展上更加得心应手。 适合人群:UGUI使用者,All 阅读方式:文章文章目录阅前提示BaseUIBehaviourEventSystem Base ...
  • [UGUI源码试探究 (一)](https://blog.csdn.net/jingangxin666/article/details/80143176)
  • UGUI 源码剖析 总结篇

    2021-09-18 11:24:04
    UGUI 源码剖析 总结篇
  • Unity中的UGUI源码解析之事件系统(1)-概述 从今天开始通过几篇文章一步步深入, 围绕事件系统展开对UGUI源码的解析. 网上大部分文章讲的是事件系统是什么, 怎么用. 我的文章会在这些基础之上进一步探讨其原理和设计...
  • UGUI源码调试方法

    2021-08-26 19:39:35
    新建一个工程,找到工程中的Package文件夹 ...将com.unity.ugui@1.0.0文件夹剪切到工程Asset文件夹,这个文件夹包含了项目的源码源码也可在C:\ProgramFiles\UnityEditor\2019.4.28f1\Editor\Data\Resour...
  • Unity UGUI 源码Graphic

    千次阅读 2022-02-12 15:39:06
    目录Graphic它是如何将我们的UI显示在屏幕上的Graphic的代码逻辑 Graphic 它是如何将我们的UI显示在屏幕上的 贴一个简单的例子 using System.Collections; using System.Collections.Generic; using UnityEngine;...
  • Physics2DRaycaster 继承自PhysicsRaycaster,其他都一样,只重写了Raycast方法,改为用Physics2D.RaycastAll来照射对象,在2017.4UGUI源码中也采用了反射的方式获取的方法,并且根据SpriteRenderer组件设置结果...
  • 二:源码解析——类头 我们在UGUI的原理中说过想要显示一个UI就需要对应的Mesh信息,那么UI的Mesh信息(顶点,三角形等)是保存到了哪里呢?这个就是VertexHelper的作用,它只是一个普通的类对象,保存了生成Mesh的...
  • UGUI 源码之 Graphic

    2021-06-13 20:08:12
    Graphic 是 UGUI 图形组件的基类。 看见1000行的代码不要头疼,阅读步骤参考 如何阅读源码: 先看 Ggraphic 的字段、属性;再看其构造方法、重写的 UIBehaviour 的生命周期方法; 再看其实现的接口 ...
  • UGUI源码(一)Canvas

    2020-09-28 17:52:20
    UGUI源码解读 --- Canvas.csCanvas.csCanvasUpdateRegistry.cs Canvas.cs //部分源码 public sealed class Canvas : Behaviour { public delegate void WillRenderCanvases(); //公有事件,在...
  • Unity如何调试UGUI源码

    2020-07-21 15:49:07
    最近通过在Unity2019中反复研究,终于获得源码调试UGUI之法,可断点调试,还可反复研究源码,美滋滋,现分享方法: 1.从unity安装路径下BuiltInPackages中复制UGUI package C:\Program Files\UnityEditor\2019.3.15...
  • github源码点击就可以 https://github.com/Unity-Technologies/uGUI 百度网盘链接 链接:https://pan.baidu.com/s/1EbFYLMz2mp2YfVhLNgy8LQ 提取码:fm72
  • UNITY5.5 自带UI系统 UGUI源码, 可以替换本地安装中的unityengine.dll文件
  • UGUI 源码之 LayoutGroup

    千次阅读 2022-03-05 20:15:28
     否则立即执行) 三、源码注释: 更多 UGUI 注释已放入 GitHub - NRatel/uGUI: Source code for the Unity UI system.。 using System.Collections; using System.Collections.Generic; using UnityEngine....
  • Unity3d:UGUI源码,Rebuild优化

    千次阅读 2022-03-23 23:00:07
    但是,大量的动静分离反而影响Canvas的合批,所以可以针对性的对战斗UI,主界面做分离 源码中查看影响重建因素 触发SetLayoutDirty Graphic: protected override void OnRectTransformDimensionsChange():当UI的...
  • UGUI源码Unity2017.3.zip

    2020-01-16 10:53:01
    UGUI源码,版本是Unity2017.3,想研究Unity底层的朋友可以下载来看看,直接用VS打开就好了。
  • 1.UGUI源码地址如下: https://bitbucket.org/Unity-Technologies/ui/downloads?tab=tags 2.找到对应版本的UGUI,然后下载zip或者其他类型的的压缩包。根据自己的喜好解压到指定目录。 3.使用文本打开README.md...
  • unity3d UGUI源码

    2017-12-27 13:52:11
    UGUI源码,是Unity3d界面开发必学知识的主要源码。可下载。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,010
精华内容 804
关键字:

ugui源码

友情链接: 875241.rar