执着等待等wc 发表于 2021-2-3 09:26

【Unity源码学习】Text源码-细品(1)

前言

老大要去生孩子了,临走还不忘关怀我,让我把Text文本组件源码读了,回来要考察我! 学习的动力+1 SoarDText组件,我们自己的扩展部分包括UGUI的源码(现在有一个乱码的bug,要是能分析出来就再好不过了); Spine的运行库源码; 二选一,自己抽时间去读,读的透透的,捋清楚里面的所有功能、细节、逻辑、算法,每一行代码都给我读懂,从原理到实现,问啥你都能知道的那个程度。 有兴趣挑战一下吗? 我这个人总喜欢功利性学习,让我们带着问题去看源码吧!问题:

字体文件是怎么使用的?封装在C里,没有找到。Material文件上没有挂texture,字体文件是怎么使用上这个文件的?fontMaterial.mainTexture = fontTexture;Text文本的居中,居左等对齐方式实现
接下来我们拿一个制作的艺术字来学习字体的制作和使用过程
一、 BMFont工具使用

详细教程: 图片导入教程设置教程
Options-Texture-设置图集大小
bit depth 设置真彩和黑白
Texture-png3. 使用教程
新建txt,输入需要使用的艺术字,以UTF-8格式保存
Edit - Selctect Form Files- 选择刚才的文本
Edit - OpenImageManager -
鼠标悬停bmfont,右下角可以显示字体ID
Image - ImportImage - 选择要导入的图片 - 输入ID - 确定二、导入unity

BM Font Maker插件 新建一个材质Material,和字体文件Custom Font,然后把生成的.png和.fnt文件拖入插件就能生成我们游戏中需要用到的字体文件
BM Font Maker工具解析了.fnt文件,转换成unity可以识别的CharacterInfo信息,存到.fontsettings文件里
我们拿1【Ascii码:49】这个美术字来做例子,分析一下这个字体文件的使用

Unity UV是以左上角为0,0点的
三、字体使用

Text

UGUI的Text就是位图字体,先通过TTF字体将字体形状生成在位图中,接着就是将正确的UV设置给字体的Mesh,这和前面介绍的Image组件几乎一样了。新建一个Text组件,设置我们自己制作的艺术字,触发FontUpdateTracker注册的textureRebuilt事件,然后调用到Text.cs的FontTextureChanged方法,里面有SetAllDirty会将m_MaterialDirty设置成true,这样,text在下次rebuild的时候会调用UpdateMaterial
Graphic.cs
      protected virtual void UpdateMaterial()
      {
            if (!IsActive())
                return;

            canvasRenderer.materialCount = 1;
            canvasRenderer.SetMaterial(materialForRendering, 0);
            canvasRenderer.SetTexture(mainTexture);
      }
至于这2个神秘的材质和纹理实现是这样的
Text.cs
      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;

                return base.mainTexture;
            }
      }
Graphic.cs
      public virtual Material materialForRendering
      {
            get
            {
                var components = ListPool<Component>.Get();
                GetComponents(typeof(IMaterialModifier), components);

                var currentMat = material;
                for (var i = 0; i < components.Count; i++)
                  currentMat = (components as IMaterialModifier).GetModifiedMaterial(currentMat);
                ListPool<Component>.Release(components);
                return currentMat;
            }
      }
Text的材质是拿的MaskableGraphic的GetModifiedMaterial的材质,是一个可以遮罩的材质,然后纹理拿的是字体文件材质的纹理
当我们进行文本输入的时候,会触发布局和顶点的重建
      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();
                }
            }
      }
TextGenerator虽然开源了C#源码,但是具体实现层层封装,最后都藏到了“Modules/TextRendering/TextGenerator.h”难过,看不到源码。只能靠大概猜想实现:
cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
应该是根据传入的text,去索引NFont.fontsettings文件的Character Rects,找到对应的索引,输出顶点和UV。
然后拿着这个顶点和UV去重建Mesh,然后根据UV信息,找到纹理上的相应位置,和大小,扣出UV,绘制到mesh上,这个具体实现应该是在shader上实现的,细节有点不懂。。。
public class Text : MaskableGraphic, ILayoutElement
{
    //...略
    //字体生成器
    public TextGenerator cachedTextGenerator
    {
      get { return m_TextCache ?? (m_TextCache = (m_Text.Length != 0 ? new TextGenerator(m_Text.Length) : new TextGenerator())); }
    }
    readonly UIVertex[] m_TempVerts = new UIVertex;
    protected override void OnPopulateMesh(VertexHelper toFill)
    {
      if (font == null)
            return;
      m_DisableFontTextureRebuiltCallback = true;
      Vector2 extents = rectTransform.rect.size;
      //获取字体的生成规则设置
      var settings = GetGenerationSettings(extents);
      //根据待填充字体、生成规则,生成顶点信息
      cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
      //取到cachedTextGenerator.verts的顶点信息
      IList<UIVertex> verts = cachedTextGenerator.verts;
      float unitsPerPixel = 1 / pixelsPerUnit;
      int vertCount = verts.Count - 4;
      if (vertCount <= 0)
      {
            toFill.Clear();
            return;
      }
      Vector2 roundingOffset = new Vector2(verts.position.x, verts.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 = verts;
                //设置字体偏移
                m_TempVerts.position *= unitsPerPixel;
                m_TempVerts.position.x += roundingOffset.x;
                m_TempVerts.position.y += roundingOffset.y;
                if (tempVertsIndex == 3)
                  //填充UI顶点面片
                  toFill.AddUIVertexQuad(m_TempVerts);
            }
      }
      else
      {
            for (int i = 0; i < vertCount; ++i)
            {
                int tempVertsIndex = i & 3;
                //填充顶点信息
                m_TempVerts = verts;
                //设置字体偏移
                m_TempVerts.position *= unitsPerPixel;
                if (tempVertsIndex == 3)
                  toFill.AddUIVertexQuad(m_TempVerts);//填充UI顶点面片
            }
      }
      m_DisableFontTextureRebuiltCallback = false;
    }
有个问题

为啥自己做的艺术字没有mesh呀,源生text都有mesh,有明白的大佬帮忙解惑一下






拓展

UGUI的Text Effect包含了字体的描边和阴影,它是如何实现的呢?
Graphic.cs(部分代码):
private void DoMeshGeneration()
    {
      if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
            //在继承类中实现具体的元素信息
            OnPopulateMesh(s_VertexHelper);
      else
            s_VertexHelper.Clear();
      var components = ListPool<Component>.Get();
      //获取当前对象是否有IMeshModifier接口
      //Text的描边和阴影都是通过IMeshModifier的ModifyMesh()实现出来的
      GetComponents(typeof(IMeshModifier), components);
      //将Text的网格信息传入ModifyMesh()进行修改
      for (var i = 0; i < components.Count; i++)
            ((IMeshModifier)components).ModifyMesh(s_VertexHelper);
      ListPool<Component>.Release(components);
      //最终Text的Mesh信息保存在s_VertexHelper对象中。
      //通过FillMesh()方法将网格构建出来
      s_VertexHelper.FillMesh(workerMesh);
      //提交网格信息,开始合并网格
      canvasRenderer.SetMesh(workerMesh);
    }
字体一旦添加描边以后会在原有基础上多绘制4遍,所以在UGUI中最好使用阴影Shadow来代替描边Outline。毕竟阴影只会多画一遍。
outline
shadow
//shadow.cs
      protected void ApplyShadowZeroAlloc(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
      {
            UIVertex vt;

            var neededCapacity = verts.Count + end - start;
            if (verts.Capacity < neededCapacity)
                verts.Capacity = neededCapacity;

            for (int i = start; i < end; ++i)
            {
                vt = verts;
                verts.Add(vt);

                Vector3 v = vt.position;
                v.x += x;
                v.y += y;
                vt.position = v;
                var newColor = color;
                if (m_UseGraphicAlpha)
                  newColor.a = (byte)((newColor.a * verts.color.a) / 255);
                vt.color = newColor;
                verts = vt;
            }
      }
//Outline继承Shadow,调用了4次ApplyShadowZeroAlloc
    public class Outline : Shadow
    {
      protected 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;

            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);
      }
    }
页: [1]
查看完整版本: 【Unity源码学习】Text源码-细品(1)