版本记录
2019-11-25 | 0.1 | 文档初始版本 | 李俊 |
原理
- rebuild过程
- 正确算出来 canvasrenderer mesh, material
- Graphic rebuild
- Graphic ugui中显示的基类
- Image 图片
- Text 文字
RectTransform.widthGraphic.OnRectTransformDimensionsChangeGraphic.SetVerticesDirtyCanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuildCanvasUpdateRegistry.PerformUpdateGraphic.RebuildGraphic.UpdateGeometry&UpdateMaterialcanvasRenderer.SetMesh&SetMaterial&SetTexturegraph TB A[RectTransform.width] B[Graphic.OnRectTransformDimensionsChange] C[Graphic.SetVerticesDirty] D[CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild] E[CanvasUpdateRegistry.PerformUpdate] F[Graphic.Rebuild] G[Graphic.UpdateGeometry&UpdateMaterial] H[canvasRenderer.SetMesh&SetMaterial&SetTexture] A-->B B-->C C-->D D-->E E-->F F-->G G-->H
- Layout Rebuild
- 计算子节点的recttransfrom
- ILayoutGroup 布局接口
- LayoutGroup 布局基类
- GridLayoutGroup 格子布局
- HorizontalLayoutGroup 水平布局
RectTransform.OnRectTransformDimensionsChangeLayoutGroup.SetDirtyLayoutRebuilder.MarkLayoutForRebuildLayoutRebuilder.MarkLayoutRootForRebuildCanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuildLayoutRebuilder.RebuildILayoutElement.CalculateLayoutInputHorizontal&SetLayoutHorizontal&CalculateLayoutInputVertical&SetLayoutVerticalgraph TB A[RectTransform.OnRectTransformDimensionsChange] B[LayoutGroup.SetDirty] C[LayoutRebuilder.MarkLayoutForRebuild] D[LayoutRebuilder.MarkLayoutRootForRebuild] E[CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild] F[LayoutRebuilder.Rebuild] G[ILayoutElement.CalculateLayoutInputHorizontal&SetLayoutHorizontal&CalculateLayoutInputVertical&SetLayoutVertical] A-->B B-->C C-->D D-->E E-->F F-->G
- 核心代码 CanvasUpdateRegistry.PerformUpdate
private void PerformUpdate()
{
UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);
CleanInvalidItems();
m_PerformingLayoutUpdate = true;
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);
}
}
}
for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
m_LayoutRebuildQueue[i].LayoutComplete();
instance.m_LayoutRebuildQueue.Clear();
m_PerformingLayoutUpdate = false;
// now layout is complete do culling...
ClipperRegistry.instance.Cull();
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);
}
}
}
for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
m_GraphicRebuildQueue[i].GraphicUpdateComplete();
instance.m_GraphicRebuildQueue.Clear();
m_PerformingGraphicUpdate = false;
UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
}
- batch过程 合并canvaserender里的mesh,这个过程在其他线程,没有源码,不可见
- 根据层次结构(深度优先)拿到所有canvasrenderer
- 从下而上遍历List,检测当前renderer和下边的renderer相交
- 没有相交, depth = 0
- 相交,其实深度值最大的为depthi,则检测是否能batch,能depth = depthi, 不能depth = depthi+1
- 所有renderer根据depth排序,depth相同的根据mat id,再根据texture id
- 相邻renderer如果能batch就合批
ui制作规范
- 原则 rebuild > batch
- 目标 减少reuild次数,减少rebuild的耗时,减少drawcall
- 动静分离 按照不同的更新频率来划分canvas 完全静态的元素放1个canvas 低频率的在1个canvas 同步高频率的在1个canvas 不同步高频率在不同的canvas
- 尽可能少用layout
- 相同图集,相同字体(相同大小),hierarchy上尽量紧挨
- 不要过深的层次结构
设置
ui常见情况处理
- 全屏界面,不好处理的地方倾向于多canvas
- 多个相同元素,建议挂辅助脚本,在awake的时候复制多个
- 循环列表带动画的这种,建议在每个item上加canvas
- 道具格建议分成几种,不要全部都用最全的
- mask尽量不用
指标
- 常在主界面 < 40
- 全屏界面 < 70
- 非全屏界面< 30
faq
- 为啥节点不易过多 因为rebuild排序时,是根据父节点的个数