Text.cs
//部分源码
public class Text : MaskableGraphic, ILayoutElement
{
//Text 组件在 Inspector 面板上显示的属性
[SerializeField] private FontData m_FontData = FontData.defaultFontData;
//TextGenerator是文本生成器,文字的大部分功能都是由这个类实现的
//Caches vertices, character info, and line info for memory friendlyness.
public TextGenerator cachedTextGenerator
{
get { return m_TextCache ?? (m_TextCache = (m_Text.Length != 0 ? new TextGenerator(m_Text.Length) : new TextGenerator())); }
}
//字体的贴图保存在Font.material.mainTexture中,通过基类Graphic.UpdateMaterial更新材质(同Image)
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;
}
}
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);
//生成的顶点数组。
IList<UIVertex> verts = cachedTextGenerator.verts;
// (float)m_FontData.fontSize / font.fontSize
float unitsPerPixel = 1 / pixelsPerUnit;
//默认字符最后一个字符是 '/n'
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;
}
//获取字体生成所需的配置对象
public TextGenerationSettings GetGenerationSettings(Vector2 extents)
{
var settings = new TextGenerationSettings();
settings.generationExtents = extents;
if (font != null && font.dynamic)
{
settings.fontSize = m_FontData.fontSize;
settings.resizeTextMinSize = m_FontData.minSize;
settings.resizeTextMaxSize = m_FontData.maxSize;
}
settings.textAnchor = m_FontData.alignment;
settings.alignByGeometry = m_FontData.alignByGeometry;
settings.scaleFactor = pixelsPerUnit;
settings.color = color;
settings.font = font;
settings.pivot = rectTransform.pivot;
settings.richText = m_FontData.richText;
settings.lineSpacing = m_FontData.lineSpacing;
settings.fontStyle = m_FontData.fontStyle;
settings.resizeTextForBestFit = m_FontData.bestFit;
settings.updateBounds = false;
settings.horizontalOverflow = m_FontData.horizontalOverflow;
settings.verticalOverflow = m_FontData.verticalOverflow;
return settings;
}
}
Shadow 与 Outline
字体阴影和描边效果:在OnPopulateMesh()更新网格之后,会遍历实现了IMeshModifier的组件,调用ModifyMesh方法修改Mesh。Shadow和Outline都实现了IMeshModifier接口,具体实现参考UGUI源码(二)Graphic.cs中的DoMeshGeneration()。
Shadow.cs
//部分源码
public class Shadow : BaseMeshEffect
{
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);
}
protected void ApplyShadow(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
{
ApplyShadowZeroAlloc(verts, color, start, end, x, y);
}
//复制了一组顶点存入verts中
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[i];
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[i].color.a) / 255);
vt.color = newColor;
verts[i] = vt;
}
}
}
Outline.cs
描边效果,继承自Shadow,只是在原来顶点基础上增加了4倍的顶点
使用Outline组件,三角面是原始网格三角面的四倍,不建议使用
//源码
public class Outline : Shadow
{
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);
}
}
Outline组件慎用,使用shader实现
Shader "UI/Outline"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_Outline ("Outline", Range(0, 1)) = 0
_OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
_OutlineAlphaMul ("OutlineAlphaMul", Range(0, 5)) = 2
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityUI.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 uv : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
UNITY_VERTEX_OUTPUT_STEREO
};
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color * _Color;
o.uv = v.uv;
o.worldPosition = v.vertex;
return o;
}
sampler2D _MainTex;
float4 _MainTex_TexelSize;
float _Outline;
fixed4 _OutlineColor;
float _OutlineAlphaMul;
fixed4 frag (v2f i) : SV_Target
{
fixed2 up = i.uv + _MainTex_TexelSize.xy * float2(0, 1) * _Outline;
fixed2 down = i.uv + _MainTex_TexelSize.xy * float2(0, -1) * _Outline;
fixed2 left = i.uv + _MainTex_TexelSize.xy * float2(-1, 0) * _Outline;
fixed2 right = i.uv + _MainTex_TexelSize.xy * float2(1, 0) * _Outline;
fixed2 ul = i.uv + _MainTex_TexelSize.xy * float2(-1, 1) * _Outline;
fixed2 ur = i.uv + _MainTex_TexelSize.xy * float2(1, 1) * _Outline;
fixed2 dl = i.uv + _MainTex_TexelSize.xy * float2(-1, -1) * _Outline;
fixed2 dr = i.uv + _MainTex_TexelSize.xy * float2(1, -1) * _Outline;
fixed4 upColor = tex2D(_MainTex, up) + _TextureSampleAdd;
fixed4 downColor = tex2D(_MainTex, down) + _TextureSampleAdd;
fixed4 leftColor = tex2D(_MainTex, left) + _TextureSampleAdd;
fixed4 rightColor = tex2D(_MainTex, right) + _TextureSampleAdd;
fixed4 ulColor = tex2D(_MainTex, ul) + _TextureSampleAdd;
fixed4 urColor = tex2D(_MainTex, ur) + _TextureSampleAdd;
fixed4 dlColor = tex2D(_MainTex, dl) + _TextureSampleAdd;
fixed4 drColor = tex2D(_MainTex, dr) + _TextureSampleAdd;
fixed4 texColor = (tex2D(_MainTex, i.uv) + _TextureSampleAdd) * i.color;
float alphaSum = upColor.a + downColor.a + leftColor.a + rightColor.a + ulColor.a + urColor.a + dlColor.a + drColor.a + texColor.a;
float alphaAva = alphaSum / 9;
float isEdge = _Outline > 0 && alphaSum > 0;
fixed4 edgeColor = texColor * alphaAva + _OutlineColor * (1 - alphaAva);
edgeColor.a = saturate(alphaAva * _OutlineAlphaMul);
fixed4 color = edgeColor * isEdge + texColor * (1 - isEdge);
color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
#if !defined(UNITY_COLORSPACE_GAMMA)
color.rgb = LinearToGammaSpace(color.rgb);
#endif
return color;
}
ENDCG
}
}
}