【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 = &#34;&#34;;
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]