untiy UGUI源码分析(3)Text

直接分析Graphic源码不太直观 ,我们从顶层分析Text控件的运作机制这样可以知道Graphic的运作机制了,首先Text继承关系如下

public class Text : MaskableGraphic, ILayoutElement

public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier

我们需要分析Text控件何时会刷新,可以肯定地是Graphic使用了脏标记模式(游戏编程

模式里面有这个设计模式),每当Text的一些属性发生改变后不会立即更新属性,而是将这些变化的属性打上标记,并在某一个时刻一起更新。

鉴于这些类比较复杂我们通过一个简单的行为去分析Text内部的运行机制。我们首先考虑当Text控件的内容发生变化时,Text控件到底执行了那些方法。通过下面代码可以看出,当Text控件的内容发生变化时会将顶点(vertex)以及布局(layout)打上标记。

public virtual string text
{
    get
    {
        return m_Text;
    }
    set
    {
        if (String.IsNullOrEmpty(value))
        {
            if (String.IsNullOrEmpty(m_Text))
                return;
            m_Text = "";
            SetVerticesDirty();
        }
        else if (m_Text != value)
        {
            m_Text = value;
            SetVerticesDirty();
            SetLayoutDirty();
        }
    }
}

我们继续分析SetVerticesDirty方法。这个方法跳到Graphic类里面来了,这个方法首先设置标志位,然后CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild 方法会向一个队列中添加一个元素,到这里Text的部分就分析完了。但是不对啊,Text控件的内容什么时候渲染更新呢,我们继续分析CanvasUpdateRegistry

return m_GraphicRebuildQueue.AddUnique(element);

public virtual void SetVerticesDirty()
{
    if (!IsActive())
        return;

    m_VertsDirty = true;
    CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

    if (m_OnDirtyVertsCallback != null)
        m_OnDirtyVertsCallback();
}

接下来我们分析 CanvasUpdateRegistry 类,他的构造函数如下会向Canvas中的willRenderCanvases静态事件添加一个回调,由于Canvas没有源码,这里我推测performUpdate这个回调会每一帧都执行更新。performUpdate这个回调的功能很简单就是根据从m_GraphicRebuildQueue以及m_LayoutRebuildQueue两个队列中取出元素,并执行每一个元素的Rebuild方法,Rebuild方法会更具不同的枚举类型进行不同的操作。Rebuild完成后执行LayoutComplete()GraphicUpdateComplete()方法。

public enum CanvasUpdate
{
  Prelayout,
  Layout,
  PostLayout,
  PreRender,
  LatePreRender,
  MaxUpdateValue,
}

protected CanvasUpdateRegistry()
{
    Canvas.willRenderCanvases += 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++)
    {
        UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);

        for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
        {
            var rebuild = m_LayoutRebuildQueue[j];
            try
            {
                if (ObjectValidForUpdate(rebuild))
                    rebuild.Rebuild((CanvasUpdate)i);
            }
            catch (Exception e)
            {
                Debug.LogException(e, rebuild.transform);
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
        m_LayoutRebuildQueue[i].LayoutComplete();

    m_LayoutRebuildQueue.Clear();
    m_PerformingLayoutUpdate = false;
    UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
    UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Render);

    // now layout is complete do culling...
    UnityEngine.Profiling.Profiler.BeginSample(m_CullingUpdateProfilerString);
    ClipperRegistry.instance.Cull();
    UnityEngine.Profiling.Profiler.EndSample();

    m_PerformingGraphicUpdate = true;

    for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
    {
        UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);
        for (var k = 0; k < m_GraphicRebuildQueue.Count; k++)
        {
            try
            {
                var element = m_GraphicRebuildQueue[k];
                if (ObjectValidForUpdate(element))
                    element.Rebuild((CanvasUpdate)i);
            }
            catch (Exception e)
            {
                Debug.LogException(e, m_GraphicRebuildQueue[k].transform);
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
        m_GraphicRebuildQueue[i].GraphicUpdateComplete();

    m_GraphicRebuildQueue.Clear();
    m_PerformingGraphicUpdate = false;
    UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Render);

经过上面的分析我们继续来看,Text的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;
    }
}

综上所述,我们可以很清楚的知道Text控件某些属性改变后的更新过程。

1.当某些属性改变时,可能时图像(Graphic)或者布局(layout)Text会进行脏标记,并向 CanvasUpdateRegistry 类中的相应队列中添加一个元素(把Text的引用添加进去,Text继承了ICanvasElement接口)。

2.CanvasUpdateRegistry 每一帧会调用PerformUpadate方法,该方法会遍历队列中的元素,并执行ICanvasElement接口的rebuild方法(Text重写了rebuild方法实际执行的时Text的rebuild方法)

3.最后在Text的rebuild方法中会根据脏标记,去更新Text的一些属性。

到这里Text的更新循环已经分析完毕了,现在继续分析Text是如何更新自己的属性,并且渲染到屏幕上。我们只分析Text的Geometry(几何可以说就是顶点)部分的更新,在rebuild中执行了UpdateGeometry方法,这个方法调用几个方法后,最终执行了DoMeshGeneration

方法。s_VertexHelper是一个工具类的实例,可以帮助生成UI的Mesh。然后DoMeshGeneration方法会执行OnPopulateMesh方法向s_VertexHelper更新Mesh,最后通过CanvasRenderer(可以在unity面板找到)组件进行渲染。

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);
}


protected override void OnPopulateMesh(VertexHelper toFill)
{
    if (font == null)
        return;

    // We don't care if we the font Texture changes while we are doing our Update.
    // The end result of cachedTextGenerator will be valid for this instance.
    // Otherwise we can get issues like Case 619238.
    m_DisableFontTextureRebuiltCallback = true;

    Vector2 extents = rectTransform.rect.size;

    var settings = GetGenerationSettings(extents);
    cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);

    // Apply the offset to the vertices
    IList<UIVertex> verts = cachedTextGenerator.verts;
    float unitsPerPixel = 1 / pixelsPerUnit;
    int vertCount = verts.Count;

    // We have no verts to process just return (case 1037923)
    if (vertCount <= 0)
    {
        toFill.Clear();
        return;
    }

    Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;
    roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;
    toFill.Clear();
    if (roundingOffset != Vector2.zero)
    {
        for (int i = 0; i < vertCount; ++i)
        {
            int tempVertsIndex = i & 3;
            m_TempVerts[tempVertsIndex] = verts[i];
            m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
            m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
            m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
            if (tempVertsIndex == 3)
                toFill.AddUIVertexQuad(m_TempVerts);
        }
    }
    else
    {
        for (int i = 0; i < vertCount; ++i)
        {
            int tempVertsIndex = i & 3;
            m_TempVerts[tempVertsIndex] = verts[i];
            m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
            if (tempVertsIndex == 3)
                toFill.AddUIVertexQuad(m_TempVerts);
        }
    }

    m_DisableFontTextureRebuiltCallback = false;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值