一、字体制作工具下载
下载安装windows下免费的位图字体制作工具Bitmap Font Generator
下载地址 http://www.angelcode.com/products/bmfont/
二、创建字体文件
清空字符
Edit->Clear all chars in font
载入新字符
Edit->Selecting text from file...
要提前准备字体文件,需注意txt文件的格式要对应,如果使用的Unicode编码就Font设置的时候就要选中Unicode。
打开软件->Options -> Font Settings
Size可以设定字体大小,需要多大就设定多大,这里是12
Height可以设定字体的拉伸高度,保持默认100%就可以了
打开软件->Options -> Export Options
Padding:文字的内边框,或者理解为文字的周边留空要多大 做后期样式时这个属性很重要,需要预留空间来给描边、发光等特效使用 比如我预计我的样式要加一个2px的边框,然后加一个右下角2px的投影效果,所以我设定了padding:2px 4px 4px 2px
BitDepth:必须32位,否则没有透明层
Presets:字体初始化的预设的颜色通道设定,也就是说字体的初始颜色设定是什么样的,建议都用白色字,可以直接设定为White text with alpha,即白色字透明底。
Font descript:字体描述文件,可以使用text或者xml 也就是fnt文件格式
Textures:纹理图片格式,一般选png。
最后设置好之后保存,会生成一个.png和一个.fnt两个文件
option->Save bitmap font as
三、Unity中生成字体
生成的png和fnt文件导入Untiy中,创建一个工具:
namespace src.test
{
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Xml;
using System;
public class BitmapFontExporter : ScriptableWizard
{
[MenuItem("BitmapFontExporter/Create")]
private static void CreateFont()
{
ScriptableWizard.DisplayWizard<BitmapFontExporter>("Create Font");
}
public TextAsset fontFile;
public Texture2D textureFile;
private void OnWizardCreate()
{
if (fontFile == null || textureFile == null)
{
return;
}
string path = EditorUtility.SaveFilePanelInProject("Save Font", fontFile.name, "", "");
if (!string.IsNullOrEmpty(path))
{
ResolveFont(path);
}
}
private void ResolveFont(string exportPath)
{
if (!fontFile) throw new UnityException(fontFile.name + "is not a valid font-xml file");
Font font = new Font();
XmlDocument xml = new XmlDocument();
xml.LoadXml(fontFile.text);
XmlNode info = xml.GetElementsByTagName("info")[0];
XmlNodeList chars = xml.GetElementsByTagName("chars")[0].ChildNodes;
CharacterInfo[] charInfos = new CharacterInfo[chars.Count];
for (int cnt = 0; cnt < chars.Count; cnt++)
{
XmlNode node = chars[cnt];
CharacterInfo charInfo = new CharacterInfo();
charInfo.index = ToInt(node, "id");
charInfo.width = ToInt(node, "xadvance");
charInfo.uv = GetUV(node);
charInfo.vert = GetVert(node);
charInfos[cnt] = charInfo;
}
Shader shader = Shader.Find("Unlit/Transparent");
Material material = new Material(shader);
material.mainTexture = textureFile;
AssetDatabase.CreateAsset(material, exportPath + ".mat");
font.material = material;
font.name = info.Attributes.GetNamedItem("face").InnerText;
font.characterInfo = charInfos;
AssetDatabase.CreateAsset(font, exportPath + ".fontsettings");
}
private Rect GetUV(XmlNode node)
{
Rect uv = new Rect();
uv.x = ToFloat(node, "x") / textureFile.width;
uv.y = ToFloat(node, "y") / textureFile.height;
uv.width = ToFloat(node, "width") / textureFile.width;
uv.height = ToFloat(node, "height") / textureFile.height;
uv.y = 1f - uv.y - uv.height;
return uv;
}
private Rect GetVert(XmlNode node)
{
Rect uv = new Rect();
uv.x = ToFloat(node, "xoffset");
uv.y = ToFloat(node, "yoffset");
uv.width = ToFloat(node, "width");
uv.height = ToFloat(node, "height");
uv.y = -uv.y;
uv.height = -uv.height;
return uv;
}
private int ToInt(XmlNode node, string name)
{
return Convert.ToInt32(node.Attributes.GetNamedItem(name).InnerText);
}
private float ToFloat(XmlNode node, string name)
{
return (float) ToInt(node, name);
}
}
}
工具生成后会在Unity窗口目录多出一个入口 BitmapFontExporter->Create
引入fnt和png两个文件
点击create后就会生成字体文件了,就可以使用到Text中了
四、文字描边脚本
创建一个Shader:
Shader "Custom/OutlineShader"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1, 1, 1, 1)
_OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
_OutlineWidth ("Outline Width", Int) = 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
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}
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
ColorMask [_ColorMask]
Pass
{
Name "OUTLINE"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _MainTex_TexelSize;
float4 _OutlineColor;
int _OutlineWidth;
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float2 texcoord1 : TEXCOORD1;
float2 texcoord2 : TEXCOORD2;
fixed4 color : COLOR;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
float2 uvOriginXY : TEXCOORD1;
float2 uvOriginZW : TEXCOORD2;
fixed4 color : COLOR;
};
v2f vert(appdata IN)
{
v2f o;
o.vertex = UnityObjectToClipPos(IN.vertex);
o.texcoord = IN.texcoord;
o.uvOriginXY = IN.texcoord1;
o.uvOriginZW = IN.texcoord2;
o.color = IN.color * _Color;
return o;
}
fixed IsInRect(float2 pPos, float2 pClipRectXY, float2 pClipRectZW)
{
pPos = step(pClipRectXY, pPos) * step(pPos, pClipRectZW);
return pPos.x * pPos.y;
}
fixed SampleAlpha(int pIndex, v2f IN)
{
const fixed sinArray[12] = { 0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5 };
const fixed cosArray[12] = { 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866 };
float2 pos = IN.texcoord + _MainTex_TexelSize.xy * float2(cosArray[pIndex], sinArray[pIndex]) * _OutlineWidth;
return IsInRect(pos, IN.uvOriginXY, IN.uvOriginZW) * (tex2D(_MainTex, pos) + _TextureSampleAdd).w * _OutlineColor.w;
}
fixed4 frag(v2f IN) : SV_Target
{
fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
if (_OutlineWidth > 0)
{
color.w *= IsInRect(IN.texcoord, IN.uvOriginXY, IN.uvOriginZW);
half4 val = half4(_OutlineColor.x, _OutlineColor.y, _OutlineColor.z, 0);
val.w += SampleAlpha(0, IN);
val.w += SampleAlpha(1, IN);
val.w += SampleAlpha(2, IN);
val.w += SampleAlpha(3, IN);
val.w += SampleAlpha(4, IN);
val.w += SampleAlpha(5, IN);
val.w += SampleAlpha(6, IN);
val.w += SampleAlpha(7, IN);
val.w += SampleAlpha(8, IN);
val.w += SampleAlpha(9, IN);
val.w += SampleAlpha(10, IN);
val.w += SampleAlpha(11, IN);
val.w = clamp(val.w, 0, 1);
color = (val * (1.0 - color.a)) + (color * color.a);
}
return color;
}
ENDCG
}
}
}
创建一个材质将Shader挂载在材质球上
创建一个OutlineEx脚本
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace src.Tool
{
/// <summary>
/// UGUI描边
/// </summary>
public class OutlineEx : BaseMeshEffect
{
public Color OutlineColor = Color.white;
[Range(0, 6)] public int OutlineWidth = 0;
private static List<UIVertex> m_VetexList = new List<UIVertex>();
protected override void Start()
{
base.Start();
var shader = Shader.Find("Custom/OutlineShader");
base.graphic.material = new Material(shader);
var v1 = base.graphic.canvas.additionalShaderChannels;
var v2 = AdditionalCanvasShaderChannels.TexCoord1;
if ((v1 & v2) != v2)
{
base.graphic.canvas.additionalShaderChannels |= v2;
}
v2 = AdditionalCanvasShaderChannels.TexCoord2;
if ((v1 & v2) != v2)
{
base.graphic.canvas.additionalShaderChannels |= v2;
}
this._Refresh();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (base.graphic.material != null)
{
this._Refresh();
}
}
#endif
private void _Refresh()
{
base.graphic.material.SetColor("_OutlineColor", this.OutlineColor);
base.graphic.material.SetInt("_OutlineWidth", this.OutlineWidth);
base.graphic.SetVerticesDirty();
}
public override void ModifyMesh(VertexHelper vh)
{
vh.GetUIVertexStream(m_VetexList);
this._ProcessVertices();
vh.Clear();
vh.AddUIVertexTriangleStream(m_VetexList);
}
private void _ProcessVertices()
{
for (int i = 0, count = m_VetexList.Count - 3; i <= count; i += 3)
{
var v1 = m_VetexList[i];
var v2 = m_VetexList[i + 1];
var v3 = m_VetexList[i + 2];
// 计算原顶点坐标中心点
//
var minX = _Min(v1.position.x, v2.position.x, v3.position.x);
var minY = _Min(v1.position.y, v2.position.y, v3.position.y);
var maxX = _Max(v1.position.x, v2.position.x, v3.position.x);
var maxY = _Max(v1.position.y, v2.position.y, v3.position.y);
var posCenter = new Vector2(minX + maxX, minY + maxY) * 0.5f;
// 计算原始顶点坐标和UV的方向
//
Vector2 triX, triY, uvX, uvY;
Vector2 pos1 = v1.position;
Vector2 pos2 = v2.position;
Vector2 pos3 = v3.position;
if (Mathf.Abs(Vector2.Dot((pos2 - pos1).normalized, Vector2.right))
> Mathf.Abs(Vector2.Dot((pos3 - pos2).normalized, Vector2.right)))
{
triX = pos2 - pos1;
triY = pos3 - pos2;
uvX = v2.uv0 - v1.uv0;
uvY = v3.uv0 - v2.uv0;
}
else
{
triX = pos3 - pos2;
triY = pos2 - pos1;
uvX = v3.uv0 - v2.uv0;
uvY = v2.uv0 - v1.uv0;
}
// 计算原始UV框
//
var uvMin = _Min(v1.uv0, v2.uv0, v3.uv0);
var uvMax = _Max(v1.uv0, v2.uv0, v3.uv0);
var uvOrigin = new Vector4(uvMin.x, uvMin.y, uvMax.x, uvMax.y);
// 为每个顶点设置新的Position和UV,并传入原始UV框
//
v1 = _SetNewPosAndUV(v1, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
v2 = _SetNewPosAndUV(v2, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
v3 = _SetNewPosAndUV(v3, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
// 应用设置后的UIVertex
//
m_VetexList[i] = v1;
m_VetexList[i + 1] = v2;
m_VetexList[i + 2] = v3;
}
}
private static UIVertex _SetNewPosAndUV(UIVertex pVertex, int pOutLineWidth,
Vector2 pPosCenter,
Vector2 pTriangleX, Vector2 pTriangleY,
Vector2 pUVX, Vector2 pUVY,
Vector4 pUVOrigin)
{
// Position
var pos = pVertex.position;
var posXOffset = pos.x > pPosCenter.x ? pOutLineWidth : -pOutLineWidth;
var posYOffset = pos.y > pPosCenter.y ? pOutLineWidth : -pOutLineWidth;
pos.x += posXOffset;
pos.y += posYOffset;
pVertex.position = pos;
// UV
var uv = pVertex.uv0;
uv += (Vector4) pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1);
uv += (Vector4) pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1);
pVertex.uv0 = uv;
// 原始UV框
pVertex.uv1 = new Vector2(pUVOrigin.x, pUVOrigin.y);
pVertex.uv2 = new Vector2(pUVOrigin.z, pUVOrigin.w);
return pVertex;
}
private static float _Min(float pA, float pB, float pC)
{
return Mathf.Min(Mathf.Min(pA, pB), pC);
}
private static float _Max(float pA, float pB, float pC)
{
return Mathf.Max(Mathf.Max(pA, pB), pC);
}
private static Vector2 _Min(Vector2 pA, Vector2 pB, Vector2 pC)
{
return new Vector2(_Min(pA.x, pB.x, pC.x), _Min(pA.y, pB.y, pC.y));
}
private static Vector2 _Max(Vector2 pA, Vector2 pB, Vector2 pC)
{
return new Vector2(_Max(pA.x, pB.x, pC.x), _Max(pA.y, pB.y, pC.y));
}
}
}
五、使用
创建一个Text挂载制作好的字体和脚本