找回密码
 立即注册
查看: 623|回复: 0

[笔记] 【Unity源码学习】Text源码-细品(1)

[复制链接]
发表于 2021-2-3 09:26 | 显示全部楼层 |阅读模式
前言

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

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

    详细教程: 图片导入教程设置教程
Options-Texture-设置图集大小
bit depth 设置真彩和黑白
Texture-png
3. 使用教程
新建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[4];
    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[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;
                //设置字体偏移
                m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
                m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
                m_TempVerts[tempVertsIndex].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[tempVertsIndex] = verts;
                //设置字体偏移
                m_TempVerts[tempVertsIndex].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);
        }
    }

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-24 09:16 , Processed in 0.135779 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表