uGUI之graphic

所有需要绘制的ui组件的抽象基类

  1. Graphic

    继承自UIBehaviour、ICanvesElement ,可渲染的ui组件

    1. ICanvasElement

      依赖Canves 的ui元素接口, 主要是rebuild接口标记

            /// <summary>
          /// Values of 'update' called on a Canvas update.
          /// </summary>
          public enum CanvasUpdate
          {
              /// <summary>
              /// Called before layout.
              /// </summary>
              Prelayout = 0,
              /// <summary>
              /// Called for layout.
              /// </summary>
              Layout = 1,
              /// <summary>
              /// Called after layout.
              /// </summary>
              PostLayout = 2,
              /// <summary>
              /// Called before rendering.
              /// </summary>
              PreRender = 3,
              /// <summary>
              /// Called late, before render.
              /// </summary>
              LatePreRender = 4,
              /// <summary>
              /// Max enum value. Always last.
              /// </summary>
              MaxUpdateValue = 5
          }
      
        /// <summary>
          /// This is an element that can live on a Canvas.
          /// </summary>
          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();
          }
    2. ICanvesElement 接口实现

    3. Rebuild

      > canvasRenderer ui渲染元素依赖组件的**render**组件
      >
      > 这里只对**渲染前**进行`顶点`和`材质`进行更新。
    
      ```csharp
             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;
                  }
              }
      ```
    1. transform
       ​ transform 沿用基类 `Component` 的transform 属性
    1. LayoutComponent
       ```csharp
       //暂时为空       
       public virtual void LayoutComplete()
       {}
       ```
    1. GraphicUpdateComplete

      //暂时为空       
      public virtual void GraphicUpdateComplete()
      {}
    2. IsDestroyed

      IsDestroyed 沿用 UIBehaviour 的IsDestroyed 属性

    3. 新增和实现

      作为需要渲染的控件, 必须有 Render、渲染的数据 顶点数据材质数据

      1. 理论部分

      2. Render 使用 CanvasRenderer

      3. 材质以及材质属性

      4. 代码部分

        1. 材质以及材质属性静态公共数据 > s_WhiteTexture静态默认贴图属性作为共享的默认贴图,OnEnable 进行赋值
        static protected Texture2D s_WhiteTexture = null;
        // 贴图赋值
        protected override void OnEnable()
        {
            // 。。。
            if (s_WhiteTexture == null)
                s_WhiteTexture = Texture2D.whiteTexture;
            // 。。。
        }

        s_DefaultUI静态默认材质属性作为共享的默认贴图,Get进行单例赋值

        static public Material defaultGraphicMaterial
        {
        get
        {
        if (s_DefaultUI == null)
        s_DefaultUI = Canvas.GetDefaultCanvasMaterial();
        return s_DefaultUI;
        }
        }

        s_VertexHelper 顶点辅助器

        [NonSerialized] private static readonly VertexHelper s_VertexHelper = new VertexHelper();
        1. 渲染控制相关

        2. 材质以及材质属性

          ```csharp
          //材质对象
          [FormerlySerializedAs("m_Mat")]
          [SerializeField] protected Material m_Material;  
        
          //材质属性 颜色
          [SerializeField] private Color m_Color = Color.white;
          ```
        1. 顶点、Mesh (辅助顶点mesh类 VertexHelper)

          Rebuild调用UpdateGeometry 进行顶点、Mesh 计算,顶点效果IMeshModifier回调触发

          OnPopulateMesh 中进行顶点属性处理,简单的顶点 和includies, 以及 uv

               public virtual void Rebuild(CanvasUpdate update)
                  {
                    ///...
                        UpdateGeometry();
                           m_VertsDirty = false;
                        ///...
                      }
                  } 
          
          /// <summary>
                  /// Call to update the geometry of the Graphic onto the CanvasRenderer.
                  /// </summary>
                  protected virtual void UpdateGeometry()
                  {
                      if (useLegacyMeshGeneration)
                      {
                          DoLegacyMeshGeneration();
                      }
                      else
                      {
                          DoMeshGeneration();
                      }
                  }
          
                  private void DoMeshGeneration()
                  {
                      if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
                          OnPopulateMesh(s_VertexHelper);
                      else
                          s_VertexHelper.Clear(); // clear the vertex helper so invalid graphics dont draw.
          
                      var components = ListPool<Component>.Get();
                      GetComponents(typeof(IMeshModifier), components);
          
                      for (var i = 0; i < components.Count; i++)
                          ((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);
          
                      ListPool<Component>.Release(components);
          
                      s_VertexHelper.FillMesh(workerMesh);
                      canvasRenderer.SetMesh(workerMesh);
                  }
          
                  private void DoLegacyMeshGeneration()
                  {
                      if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
                      {
          #pragma warning disable 618
                          OnPopulateMesh(workerMesh);
          #pragma warning restore 618
                      }
                      else
                      {
                          workerMesh.Clear();
                      }
          
                      var components = ListPool<Component>.Get();
                      GetComponents(typeof(IMeshModifier), components);
          
                      for (var i = 0; i < components.Count; i++)
                      {
          #pragma warning disable 618
                          ((IMeshModifier)components[i]).ModifyMesh(workerMesh);
          #pragma warning restore 618
                      }
          
                      ListPool<Component>.Release(components);
                      canvasRenderer.SetMesh(workerMesh);
                  }
          
               // 提供虚函数,改变顶点属性
                  protected virtual void OnPopulateMesh(VertexHelper vh)
                  {
                      var r = GetPixelAdjustedRect();
                      var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);
          
                      Color32 color32 = color;
                      vh.Clear();
                      vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(0f, 0f));
                      vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(0f, 1f));
                      vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(1f, 1f));
                      vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(1f, 0f));
          
                      vh.AddTriangle(0, 1, 2);
                      vh.AddTriangle(2, 3, 0);
                  }
        2. 设置Render的贴图和材质属性

                  protected virtual void UpdateMaterial()
                  {
                      if (!IsActive())
                          return;
          
                      canvasRenderer.materialCount = 1;
                      canvasRenderer.SetMaterial(materialForRendering, 0);
                      canvasRenderer.SetTexture(mainTexture);
                  }
        3. Rebuild

          1. GraphicRegistry 按顺序注册缓存
        
             > 储存渲染元素, 以`Canvas`为Key,IndexedSet<Graphic> 为value 的字典
             >
             > 这个Canvas 是父节点遍历的第一个Canvas
        
             ```csharp
             private readonly Dictionary<Canvas, IndexedSet<Graphic>> m_Graphics = new Dictionary<Canvas, IndexedSet<Graphic>>();
             ```
        
             1. 注册 GraphicRegistry.RegisterGraphicForCanvas, 静态方法内部处理使用 内部静态对象 s_Instance
        
                > 按顺序添加到 Canvas 对应的Graphic 列表中
        
                1. void RegisterGraphicForCanvas(Canvas c, Graphic graphic)
        
             2. GetGraphicsForCanvas(Canvas c)
        
                > 或者对应的Canvas 的Graphic 的元素列表
        
          2. CanvasUpdateRegistry
        
             > 分为两大类 :
             >
             > 1. IndexedSet<ICanvasElement> m_LayoutRebuildQueue
             >
             > 2. IndexedSet<ICanvasElement> m_GraphicRebuildQueue 
             >
             >    主要提供注册和移除接口, 内部处理使用 Canvas.willRenderCanvases += PerformUpdate; 回调处理
             >
             >    ~~,因此流程时序很重要~~。
        
             1. RegisterCanvasElementForGraphicRebuild 
        
             2. RegisterCanvasElementForLayoutRebuild
        
             3. UnRegisterCanvasElementForRebuild, 移除layout和graphic队列
        
             4. PerformUpdate
        
                > 主要步骤如下:
        
                ```csharp
                public enum CanvasUpdate
                    {
                        /// <summary>
                        /// Called before layout.
                        /// </summary>
                        Prelayout = 0,
                        /// <summary>
                        /// Called for layout.
                        /// </summary>
                        Layout = 1,
                        /// <summary>
                        /// Called after layout.
                        /// </summary>
                        PostLayout = 2,
                        /// <summary>
                        /// Called before rendering.
                        /// </summary>
                        PreRender = 3,
                        /// <summary>
                        /// Called late, before render.
                        /// </summary>
                        LatePreRender = 4,
                        /// <summary>
                        /// Max enum value. Always last.
                        /// </summary>
                        MaxUpdateValue = 5
                    }
                ```
        
                验证非null的UnityObject
        
                ```csharp
                 private bool ObjectValidForUpdate(ICanvasElement element)
                        {
                            var valid = element != null;
        
                            var isUnityObject = element is Object;
                            if (isUnityObject)
                                valid = (element as Object) != null; //Here we make use of the overloaded UnityEngine.Object == null, that checks if the native object is alive.
        
                            return valid;
                        }
                ```
        
        
        
        
        
                1. CleanInvalidItems 移除量大队列的null 元素和标记为销毁的元素, **标记销毁是因为引擎设计销毁操作在下一帧的缘故**
        
                2.  layout队列 根据transform层级排序(父节点数作为的层级)。进行两次迭代验证非null的UnityObject并 执行 Rebuild回调的 Prelayout, 和Rebuild回调的 Layout. 最后遍历回调 LayoutComplete。 清理Layout 临时引用。
                   ```csharp
                   m_PerformingLayoutUpdate = true;
                   m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
                   ```                    
                   ```csharp
                           private static int ParentCount(Transform child)
                           {
                               if (child == null)
                                   return 0;
        
                               var parent = child.parent;
                               int count = 0;
                               while (parent != null)
                               {
                                   count++;
                                   parent = parent.parent;
                               }
                               return count;
                           }
                   ```
                   ```csharp
                         private bool ObjectValidForUpdate(ICanvasElement element)
                           {
                               var valid = element != null;
        
                               var isUnityObject = element is Object;
                               if (isUnityObject)
                                   valid = (element as Object) != null; //Here we make use of the overloaded UnityEngine.Object == null, that checks if the native object is alive.
        
                               return valid;
                           }
                   ```
                   ```csharp
                    m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
                               for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++)
                               {
                                   for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
                                   {
                                       var rebuild = instance.m_LayoutRebuildQueue[j];
                                       try
                                       {
                                           if (ObjectValidForUpdate(rebuild))
                                               rebuild.Rebuild((CanvasUpdate)i);
                                       }
                                       catch (Exception e)
                                       {
                                           Debug.LogException(e, rebuild.transform);
                                       }
                                   }
                               }
                   ```
                   ```csharp
                   for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
                        m_LayoutRebuildQueue[i].LayoutComplete();
                   ```
                   ```csharp
                   instance.m_LayoutRebuildQueue.Clear();
                   m_PerformingLayoutUpdate = false;
                   ```
        
                3.  执行 Cull
                   ```csharp
                   // now layout is complete do culling...
                   ClipperRegistry.instance.Cull();
                   ```
        
        
        
                4. Graphic队列 ,进行两次迭代验证非null的UnityObject并 执行 Rebuild回调的 PreRender, 和Rebuild回调的 LatePreRender. 最后遍历回调 GraphicUpdateComplete。 清理Graphic临时引用。
                   ```csharp
                   m_PerformingGraphicUpdate = true;
                               for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
                               {
                                   for (var k = 0; k < instance.m_GraphicRebuildQueue.Count; k++)
                                   {
                                       try
                                       {
                                           var element = instance.m_GraphicRebuildQueue[k];
                                           if (ObjectValidForUpdate(element))
                                               element.Rebuild((CanvasUpdate)i);
                                       }
                                       catch (Exception e)
                                       {
                                           Debug.LogException(e, instance.m_GraphicRebuildQueue[k].transform);
                                       }
                                   }
                               }
                   ```
                   ```csharp
                   for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
                                   m_GraphicRebuildQueue[i].GraphicUpdateComplete();
                   ```
                   ```csharp
                   instance.m_GraphicRebuildQueue.Clear();
                    m_PerformingGraphicUpdate = false;
                   ```
        
          3. LayoutRebuilder, Layout集中的 Rebuild处理
        
             > CanvasElement 包装器,专门处理Layout的rebuild,
             >
             > 主要接口 `MarkLayoutForRebuild`, 这个包装只处理包含并激活ILayoutGroup的RectTransform
             >
             > 最后调用 `TryRegisterCanvasElementForLayoutRebuild` 处理
        
             1. 被触发的方法
        
                ```csharp
                protected override void OnBeforeTransformParentChanged()
                {
                   GraphicRegistry.UnregisterGraphicForCanvas(canvas, this);
                    LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
                }
                ```
        
                ```csharp
                public virtual void SetLayoutDirty()
                {
                   if (!IsActive())
                       return;
                   LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
                   if (m_OnDirtyLayoutCallback != null)
                       m_OnDirtyLayoutCallback();
                }
                ```
        
                ```csharp
                   protected override void OnDisable()
                   {
                       //。。。
                       LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
                       //。。。
                   }
                ```
        
        
        
             2. 注册的回调事件
        
                ```csharp
        
                static LayoutRebuilder()
                {
                   RectTransform.reapplyDrivenProperties += ReapplyDrivenProperties;
                }
        
                //...
                static void ReapplyDrivenProperties(RectTransform driven)
                {
                   MarkLayoutForRebuild(driven);
                }
                ```
        
             3.  MarkLayoutForRebuild 实现细节
        
                ```csharp
                public static void MarkLayoutForRebuild(RectTransform rect)
                {
                   if (rect == null || rect.gameObject == null)
                       return;
        
                   var comps = ListPool<Component>.Get();
                    bool validLayoutGroup = true;
                    RectTransform layoutRoot = rect;
                    var parent = layoutRoot.parent as RectTransform;
                    //如果父节点含有 ILayoutGroup, 则 layoutRoot = parent,
                    //parent = parent'parent
                    while (validLayoutGroup && !(parent == null || parent.gameObject == null))
                    {
                       validLayoutGroup = false;
                       parent.GetComponents(typeof(ILayoutGroup), comps);
        
                        for (int i = 0; i < comps.Count; ++i)
                        {
                             var cur = comps[i];
                             if (cur != null && cur is Behaviour && ((Behaviour)cur).isActiveAndEnabled)
                             {
                                  validLayoutGroup = true;
                                  layoutRoot = parent;
                                  break;
                              }
                           }
        
                           parent = parent.parent as RectTransform;
                    }
        
                            // We know the layout root is valid if it's not the same as the rect,
                            // since we checked that above. But if they're the same we still need to check.
                    //1. 不含有 ILayoutGroup组件的情况, 或者ILayoutGroup没有激活的情况
                   if (layoutRoot == rect && !ValidController(layoutRoot, comps))
                    {
                       ListPool<Component>.Release(comps);
                        return;
                     }
        
                    //2. 包含 ILayoutGroup且控件激活
                     MarkLayoutRootForRebuild(layoutRoot);
                     ListPool<Component>.Release(comps);
                }
                ```
        
                ```csharp
                   private static void MarkLayoutRootForRebuild(RectTransform controller)
                        {
                            if (controller == null)
                                return;
        
                            var rebuilder = s_Rebuilders.Get();
                            rebuilder.Initialize(controller);
                            if (!CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild(rebuilder))
                                s_Rebuilders.Release(rebuilder);
                        }
                ```
        
             4. 设置回调, 对应设置徐然数据改变最后的回调方法
        
                1. RegisterDirtyLayoutCallback
                2. UnregisterDirtyLayoutCallback
                3. RegisterDirtyVerticesCallback
                4. UnregisterDirtyVerticesCallback
                5. RegisterDirtyMaterialCallback
                6. UnregisterDirtyMaterialCallback
        
          4. ~~GraphicRebuildTracker.TrackGraphic~~(this);
        1. 物理以及事件

        Raycast、

        1. m_RaycastTarget,启动input事件检测

        2. Raycast 方法,用于事件监测,Canvas ,eventsystem

           public virtual bool Raycast(Vector2 sp, Camera eventCamera)
                  {
                      if (!isActiveAndEnabled)
                          return false;
          
                      var t = transform;
                      var components = ListPool<Component>.Get();
          
                      bool ignoreParentGroups = false;
                      bool continueTraversal = true;
          
                      while (t != null)
                      {
                          t.GetComponents(components);
                          for (var i = 0; i < components.Count; i++)
                          {
                              var canvas = components[i] as Canvas;
                              if (canvas != null && canvas.overrideSorting)
                                  continueTraversal = false;
          
                              var filter = components[i] as ICanvasRaycastFilter;
          
                              if (filter == null)
                                  continue;
          
                              var raycastValid = true;
          
                              var group = components[i] as CanvasGroup;
                              if (group != null)
                              {
                                  if (ignoreParentGroups == false && group.ignoreParentGroups)
                                  {
                                      ignoreParentGroups = true;
                                      raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
                                  }
                                  else if (!ignoreParentGroups)
                                      raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
                              }
                              else
                              {
                                  raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
                              }
          
                              if (!raycastValid)
                              {
                                  ListPool<Component>.Release(components);
                                  return false;
                              }
                          }
                          t = continueTraversal ? t.parent : null;
                      }
                      ListPool<Component>.Release(components);
                      return true;
                  }
        3. 基础Tween动画

          1. CrossFadeColor、
          2. CrossFadeColor、
          3. CreateColorFromAlpha、
          4. CrossFadeAlpha
        4. 相对坐标像素计算

          1. PixelAdjustPoint、
          2. GetPixelAdjustedRect
        5. 辅助类 Pool, IndexedSet


文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录