UGUI源码分析:Text与Outline的具体实现

系列

UGUI源码分析系列总览
相关前置:
UGUI EventSystem源码分析
Graphic源码分析


Text

BaseClass: MaskableGraphic

Interface: ,ILayoutElement

Intro: UGUI中重要的组件之一, 根据字符串显示文本的组件

  • ILayoutElement:布局元素,可以被布局组件(ILayoutController)布局

在这里插入图片描述


FontData

在这里插入图片描述

Text,相比大家也再熟悉不过了。我们经常使用它来显示文字信息,并且Text组件提供了许多参数丰富显示内容与状态。这些参数主要来自于Text中的FontData

[SerializeField] private FontData m_FontData = FontData.defaultFontData;

public class FontData : ISerializationCallbackReceiver
{
    //当这些值发生改变时,会触发Text的重建
    private Font m_Font; //字体
    private int m_FontSize; //字号
    private FontStyle m_FontStyle; //字形(加粗、倾斜)
    private bool m_BestFit; //设置最佳尺寸(根据矩形框大小改变字号大小),和overflow互斥
    private int m_MinSize;  // bestFit 的最小尺寸
    private int m_MaxSize;	// bestFit 的最大尺寸
    private TextAnchor m_Alignment; //文本对齐(控制文本居中/居右/居左...)
    private bool m_AlignByGeometry; //更具字符产生的矩形范围来进行对齐(下文具体介绍)
    private bool m_RichText; //富文本
    private HorizontalWrapMode m_HorizontalOverflow; //水平溢出
    private VerticalWrapMode m_VerticalOverflow; // 垂直溢出
    private float m_LineSpacing; // 行间距
	
    //默认参数设置
    public static FontData defaultFontData
    {
        get
        {
            var fontData = new FontData
            {
                m_FontSize  = 14,
                m_LineSpacing = 1f,
                m_FontStyle = FontStyle.Normal,
                m_BestFit = false,
                m_MinSize = 10,
                m_MaxSize = 40,
                m_Alignment = TextAnchor.UpperLeft,
                m_HorizontalOverflow = HorizontalWrapMode.Wrap,
                m_VerticalOverflow = VerticalWrapMode.Truncate,
                m_RichText  = true,
                m_AlignByGeometry = false
            };
            return fontData;
        }
    }

    ......
}

AlignByGeometry:

一般的,因为在不同字体中,文字的长宽是不相等的(如果相等的话那文字之间的间距就会不一样),所以文本对齐的实现是选择字体中的长宽最大值为参考来进行对齐的,如图所示,采取的是左对齐与上对齐,我们可以看见字与矩形的边界还是有一定距离的。

在这里插入图片描述

除了以上所说的常规对齐模式,Text还提供了一个布尔值变量AlignByGeometry来改变对齐模式。

在AlignByGeometry开启的情况下,Text会以当前所显示的文字中获取最大长宽作为参考(而不是字体中的长宽),进行对齐。(图中锁定的是“$”字符,使其贴边

在这里插入图片描述

当“$”字符被删除后,会变更长宽参考,重新对齐。

在这里插入图片描述


Text的生命周期

Enable时会将 cachedTextGenerator设置成无效(这会使TextGenerator在调用 Populate 时强制生成全文本)。并向FontUpdateTracker注册自己(当Unity需要刷新字体资源时,用来帮助刷新Text组件)。

Disable时将自身移除出FontUpdateTracker

**TextGenerator:**Unity专门用来渲染文字的类,在UnityEngine.dll。可以说Text的代码量并不是很多的原因是由该类完成了文本显示所需的顶点、字符信息、行信息的计算。


OnPopulateMesh

​ 进入这里,便是Text最为核心的方法,网格处理。不同于Image不仅要处理网格还要处理材质和纹理,Text并不需要对材质纹理做更加具体的处理,因为Text的材质默认来自于它的字体本身或是它身上所被设置的材质。

public override Texture mainTexture
{
    get
    {
        if (font != null && font.material != null && font.material.mainTexture != null)
            return font.material.mainTexture; //字体的材质

        if (m_Material != null)
            return m_Material.mainTexture; // 继承自Graphic,可以手动添加的材质

        return base.mainTexture;
    }
}

Text重载了Graphic的OnPopulateMesh方法,该方法是几何更新生成网格(Mesh)的重要方法。通过TextGenerator计算顶点数量与位置(每个字符由四个顶点构成两个三角面)。

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

    Vector2 extents = rectTransform.rect.size;

    var settings = GetGenerationSettings(extents);
    //根据字符串和FontData中的设置计算顶点数据
    cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
    // Apply the offset to the vertices
    IList<UIVertex> verts = cachedTextGenerator.verts;
    //m_FontData.fontSize/font.fontSize 字号放大缩小字体的具体实现来自这里,改变顶点位置
    float unitsPerPixel = 1 / pixelsPerUnit; 
    //Last 4 verts are always a new line... (\n)
    //这里可以看出来TextGenerator会生成 (字符数+1)*4 的顶点数据
    int vertCount = verts.Count - 4;

     //计算距离矩形边框的偏移量
    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;
}

Shadow 与 Outline 具体实现

想必在使用Text的过程中,多多少少需要实现字体描边的效果吧。UGUI中为我们提供了实现描边效果的组件Outline。接下来我们就具体分析一些Outline与其父级Shadow的具体实现。

Shadow

BaseClass: BaseMeshEffect

Interface: None

Intro: UGUI实现阴影效果组件

基于网格实现阴影效果,在Graphic中DoMeshGeneration方法中(UpdateGeometry流程,具体过程请查看Graphic源码分析)会遍历所有IMeshModifier组件来更新最终的网格数据(VertexHelper)。

具体实现为:根据之前顶点信息复制并进行偏移与改变颜色生成新的顶点并添加进当前网格数据中。

即:使用Shadow组件后,顶点数三角面数增加了一倍

public override void ModifyMesh(VertexHelper vh)
{
    if (!IsActive())
        return;
    var output = ListPool<UIVertex>.Get();
    vh.GetUIVertexStream(output);//获取当前的顶点数据

    //通过该方法计算出新的顶点数据:复制当前顶点并,根据effectDistance位置偏移,根据effectColor改变颜色。 
    ApplyShadow(output, effectColor, 0, output.Count, effectDistance.x, effectDistance.y);
    vh.Clear();
    //添加进当前网格数据中
    vh.AddUIVertexTriangleStream(output);
    ListPool<UIVertex>.Release(output);
}

如图中效果:
在这里插入图片描述


Outline

BaseClass: Shadow

Interface: None

Intro: UGUI实现描边效果组件

基于网格实现描边效果,继承自Shadow,仅仅只是在顶点处理上增加了4倍的顶点量。

具体实现为:根据之前顶点信息复制并进行偏移与改变颜色生成新的顶点并添加进当前网格数据中。

即:使用Outline组件后,顶点数三角面数增加了四倍

public override void ModifyMesh(VertexHelper vh)
{
    if (!IsActive())
        return;
    var verts = ListPool<UIVertex>.Get();
    vh.GetUIVertexStream(verts);
    var neededCpacity = verts.Count * 5;
    if (verts.Capacity < neededCpacity)
        verts.Capacity = neededCpacity;
    
    //改变偏移数据生成4倍的顶点数据
    var start = 0;
    var end = verts.Count;
    ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, effectDistance.y);

    start = end;
    end = verts.Count;
    ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, -effectDistance.y);

    start = end;
    end = verts.Count;
    ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, effectDistance.y);

    start = end;
    end = verts.Count;
    ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, -effectDistance.y);

    vh.Clear();
    //添加进原始数据中
    vh.AddUIVertexTriangleStream(verts);
    ListPool<UIVertex>.Release(verts);
}

如图中效果:
在这里插入图片描述


嗨,我是作者Vin129,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值