下载Bitmap Font Generator
BMFont - AngelCode.com
解压后不用安装直接双击使用
提前设置
1、设置Bit depth为32
Options->Export options
2、清空所选字符
因为我们将在后边导入需要的字符。
Edit->Select all chars 先选择所有字符
Edit->Clear all chars in font 再清空所有字符
配置字符
1、打开Open Image Manager
Edit->Open Image Manager
2、配置字符和对应的字符图片
先准备这些字符的ASCII码
可以通过在线工具查询:ASCII编码转换,ASCII码在线查询工具
字符 | ASCII码 |
0 | 48 |
1 | 49 |
2 | 50 |
3 | 51 |
4 | 52 |
5 | 53 |
6 | 54 |
7 | 55 |
8 | 56 |
9 | 57 |
. | 46 |
+ | 43 |
- | 45 |
K | 75 |
M | 77 |
G | 71 |
点击Image->Import image,开始配置
返回主界面,查看标有小亮点的字符为已配置成功的字符
导出文件
导出前可以预览一下 Options->Visualize
直接导出
Options->Save bitmap font as....
导出成功
导入Unity使用
创建下面两个脚本,将其放到Editor文件夹下
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
public class BFImporter : AssetPostprocessor
{
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (string str in importedAssets)
{
DoImportBitmapFont(str);
}
foreach (string str in deletedAssets)
{
DelBitmapFont(str);
}
for (var i = 0; i < movedAssets.Length; i++)
{
MoveBitmapFont(movedFromAssetPaths[i], movedAssets[i]);
}
}
public static bool IsFnt(string path)
{
return path.EndsWith(".fnt", StringComparison.OrdinalIgnoreCase);
}
public static void DoImportBitmapFont(string fntPath)
{
if (!IsFnt(fntPath)) return;
TextAsset fnt = AssetDatabase.LoadMainAssetAtPath(fntPath) as TextAsset;
string text = fnt.text;
FntParse parse = FntParse.GetFntParse(ref text);
if (parse == null) return;
string fntName = Path.GetFileNameWithoutExtension(fntPath);
string rootPath = Path.GetDirectoryName(fntPath);
string fontPath = string.Format("{0}/{1}.fontsettings", rootPath, fntName);
Texture2D[] textures = DoImportTextures(parse, rootPath, fnt);
if (textures.Length > 1)
{
Debug.LogError(fntPath + " has more than one texture!");
}
Font font = AssetDatabase.LoadMainAssetAtPath(fontPath) as Font;
if (font == null)
{
font = new Font();
AssetDatabase.CreateAsset(font, fontPath);
AssetDatabase.WriteImportSettingsIfDirty(fontPath);
AssetDatabase.ImportAsset(fontPath);
}
Material material = AssetDatabase.LoadAssetAtPath(fontPath, typeof(Material)) as Material;
if (material == null)
{
material = new Material(Shader.Find("UI/Default"));
material.name = "Font Material";
AssetDatabase.AddObjectToAsset(material, fontPath);
// unity 5.4+ cannot refresh it immediately, must import it
AssetDatabase.ImportAsset(fontPath);
}
font.material = material;
material.mainTexture = textures[0];
font.characterInfo = parse.charInfos;
SerializedObject so = new SerializedObject(font);
so.Update();
so.FindProperty("m_FontSize").floatValue = Mathf.Abs(parse.fontSize);
so.FindProperty("m_LineSpacing").floatValue = parse.lineHeight;
so.FindProperty("m_Ascent").floatValue = parse.lineBaseHeight;
SerializedProperty prop = so.FindProperty("m_Descent");
if (prop != null)
prop.floatValue = parse.lineBaseHeight - parse.lineHeight;
UpdateKernings(so, parse.kernings);
so.ApplyModifiedProperties();
so.SetIsDifferentCacheDirty();
AssetDatabase.DeleteAsset(fntPath);
AssetDatabase.SaveAssets();
// unity 5.5 can not load custom font
ReloadFont(fontPath);
}
private static Texture2D[] DoImportTextures(FntParse parse, string rootPath, TextAsset fnt)
{
int len = parse.textureNames.Length;
Texture2D[] textures = new Texture2D[len];
for (int i = 0; i < len; i++)
{
string texPath = string.Format("{0}/{1}", rootPath, parse.textureNames[i]);
Texture2D texture = AssetDatabase.LoadMainAssetAtPath(texPath) as Texture2D;
if (texture == null)
{
Debug.LogErrorFormat(fnt, "{0}: not found '{1}'.", typeof(BFImporter), texPath);
return textures;
}
TextureImporter texImporter = AssetImporter.GetAtPath(texPath) as TextureImporter;
texImporter.textureType = TextureImporterType.GUI;
texImporter.mipmapEnabled = false;
texImporter.SaveAndReimport();
textures[i] = texture;
}
return textures;
}
private static void UpdateKernings(SerializedObject so, Kerning[] kernings)
{
int len = kernings != null ? kernings.Length : 0;
SerializedProperty kerningsProp = so.FindProperty("m_KerningValues");
if (len == 0)
{
kerningsProp.ClearArray();
return;
}
int propLen = kerningsProp.arraySize;
for (int i = 0; i < len; i++)
{
if (propLen <= i)
{
kerningsProp.InsertArrayElementAtIndex(i);
}
SerializedProperty kerningProp = kerningsProp.GetArrayElementAtIndex(i);
kerningProp.FindPropertyRelative("second").floatValue = kernings[i].amount;
SerializedProperty pairProp = kerningProp.FindPropertyRelative("first");
pairProp.Next(true);
pairProp.intValue = kernings[i].first;
pairProp.Next(false);
pairProp.intValue = kernings[i].second;
}
for (int i = propLen - 1; i >= len; i--)
{
kerningsProp.DeleteArrayElementAtIndex(i);
}
}
private static void DelBitmapFont(string fntPath)
{
if (!IsFnt(fntPath)) return;
string fontPath = fntPath.Substring(0, fntPath.Length - 4) + ".fontsettings";
AssetDatabase.DeleteAsset(fontPath);
}
private static void MoveBitmapFont(string oldFntPath, string nowFntPath)
{
if (!IsFnt(nowFntPath)) return;
string oldFontPath = oldFntPath.Substring(0, oldFntPath.Length - 4) + ".fontsettings";
string nowFontPath = nowFntPath.Substring(0, nowFntPath.Length - 4) + ".fontsettings";
AssetDatabase.MoveAsset(oldFontPath, nowFontPath);
}
// new font can not display via Text in unity 5.5
// must import import it
private static void ReloadFont(string fontPath)
{
var tmpPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
AssetDatabase.ExportPackage(fontPath, tmpPath);
AssetDatabase.DeleteAsset(fontPath);
var startTime = DateTime.Now;
EditorApplication.CallbackFunction func = null;
func = () =>
{
TimeSpan dalt = DateTime.Now - startTime;
if (dalt.TotalSeconds >= 0.1)
{
EditorApplication.update -= func;
AssetDatabase.ImportPackage(tmpPath, false);
File.Delete(tmpPath);
}
};
EditorApplication.update += func;
}
}
using UnityEngine;
using System.Xml;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public struct Kerning
{
public int first;
public int second;
public int amount;
}
public class FntParse
{
public int textureWidth;
public int textureHeight;
public string[] textureNames;
public string fontName;
public int fontSize;
public int lineHeight;
public int lineBaseHeight;
public CharacterInfo[] charInfos { get; private set; }
public Kerning[] kernings { get; private set; }
public static FntParse GetFntParse(ref string text)
{
FntParse parse = null;
if (text.StartsWith("info"))
{
parse = new FntParse();
parse.DoTextParse(ref text);
}
else if (text.StartsWith("<"))
{
parse = new FntParse();
parse.DoXMLPase(ref text);
}
return parse;
}
#region xml
public void DoXMLPase(ref string content)
{
XmlDocument xml = new XmlDocument();
xml.LoadXml(content);
XmlNode info = xml.GetElementsByTagName("info")[0];
XmlNode common = xml.GetElementsByTagName("common")[0];
XmlNodeList pages = xml.GetElementsByTagName("pages")[0].ChildNodes;
XmlNodeList chars = xml.GetElementsByTagName("chars")[0].ChildNodes;
fontName = info.Attributes.GetNamedItem("face").InnerText;
fontSize = ToInt(info, "size");
lineHeight = ToInt(common, "lineHeight");
lineBaseHeight = ToInt(common, "base");
textureWidth = ToInt(common, "scaleW");
textureHeight = ToInt(common, "scaleH");
int pageNum = ToInt(common, "pages");
textureNames = new string[pageNum];
for (int i = 0; i < pageNum; i++)
{
XmlNode page = pages[i];
int pageId = ToInt(page, "id");
textureNames[pageId] = page.Attributes.GetNamedItem("file").InnerText;
}
charInfos = new CharacterInfo[chars.Count];
for (int i = 0; i < chars.Count; i++)
{
XmlNode charNode = chars[i];
charInfos[i] = CreateCharInfo(
ToInt(charNode, "id"),
ToInt(charNode, "x"),
ToInt(charNode, "y"),
ToInt(charNode, "width"),
ToInt(charNode, "height"),
ToInt(charNode, "xoffset"),
ToInt(charNode, "yoffset"),
ToInt(charNode, "xadvance"),
ToInt(charNode, "page"));
}
// kernings
XmlNode kerningsNode = xml.GetElementsByTagName("kernings")[0];
if (kerningsNode != null && kerningsNode.HasChildNodes)
{
XmlNodeList kerns = kerningsNode.ChildNodes;
kernings = new Kerning[kerns.Count];
for (int i = 0; i < kerns.Count; i++)
{
XmlNode kerningNode = kerns[i];
kernings[i] = new Kerning();
kernings[i].first = ToInt(kerningNode, "first");
kernings[i].second = ToInt(kerningNode, "second");
kernings[i].amount = ToInt(kerningNode, "amount");
}
}
}
private static int ToInt(XmlNode node, string name)
{
return int.Parse(node.Attributes.GetNamedItem(name).InnerText);
}
#endregion
#region text
private Regex pattern;
public void DoTextParse(ref string content)
{
// letter=" " // \S+=".+?"
// letter="x" // \S+=".+?"
// letter=""" // \S+=".+?"
// letter="" // \S+
// char // \S+
pattern = new Regex(@"\S+="".+?""|\S+");
string[] lines = content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
ReadTextInfo(ref lines[0]);
ReadTextCommon(ref lines[1]);
for (int j = 0; j < textureNames.Length; j++)
{
ReadTextPage(ref lines[j + 2]);
}
// don't use count of chars, count is incorrect if has space
//ReadTextCharCount(ref lines[3]);
List<CharacterInfo> list = new List<CharacterInfo>();
int i = 2 + textureNames.Length;
int l = lines.Length;
for (; i < l; i++)
{
if (!ReadTextChar(i - 4, ref lines[i], ref list))
break;
}
charInfos = list.ToArray();
// skip empty line
for (; i < l; i++)
{
if (lines[i].Length > 0)
break;
}
// kernings
if (i < l)
{
int count = 0;
if (ReadTextCount(ref lines[i++], out count))
{
int start = i;
kernings = new Kerning[count];
for (; i < l; i++)
{
if (!ReadTextKerning(i - start, ref lines[i], ref list))
break;
}
};
}
}
private void ReadTextInfo(ref string line)
{
string[] keys;
string[] values;
SplitParts(line, out keys, out values);
for (int i = keys.Length - 1; i >= 0; i--)
{
switch (keys[i])
{
case "face": fontName = values[i]; break;
case "size": fontSize = int.Parse(values[i]); break;
}
}
}
private void ReadTextCommon(ref string line)
{
string[] keys;
string[] values;
SplitParts(line, out keys, out values);
for (int i = keys.Length - 1; i >= 0; i--)
{
switch (keys[i])
{
case "lineHeight": lineHeight = int.Parse(values[i]); break;
case "base": lineBaseHeight = int.Parse(values[i]); break;
case "scaleW": textureWidth = int.Parse(values[i]); break;
case "scaleH": textureHeight = int.Parse(values[i]); break;
case "pages": textureNames = new string[int.Parse(values[i])]; break;
}
}
}
private void ReadTextPage(ref string line)
{
string[] keys;
string[] values;
SplitParts(line, out keys, out values);
string textureName = null;
int pageId = -1;
for (int i = keys.Length - 1; i >= 0; i--)
{
switch (keys[i])
{
case "file": textureName = values[i]; break;
case "id": pageId = int.Parse(values[i]); break;
}
}
textureNames[pageId] = textureName;
}
private bool ReadTextCount(ref string line, out int count)
{
string[] keys;
string[] values;
SplitParts(line, out keys, out values);
count = 0;
for (int i = keys.Length - 1; i >= 0; i--)
{
switch (keys[i])
{
case "count":
count = int.Parse(values[i]);
return true;
}
}
return false;
}
private bool ReadTextChar(int idx, ref string line, ref List<CharacterInfo> list)
{
if (!line.StartsWith("char")) return false;
string[] keys;
string[] values;
SplitParts(line, out keys, out values);
int id = 0, x = 0, y = 0, w = 0, h = 0, xo = 0, yo = 0, xadvance = 0;
for (int i = keys.Length - 1; i >= 0; i--)
{
switch (keys[i])
{
case "id": id = int.Parse(values[i]); break;
case "x": x = int.Parse(values[i]); break;
case "y": y = int.Parse(values[i]); break;
case "width": w = int.Parse(values[i]); break;
case "height": h = int.Parse(values[i]); break;
case "xoffset": xo = int.Parse(values[i]); break;
case "yoffset": yo = int.Parse(values[i]); break;
case "xadvance": xadvance = int.Parse(values[i]); break;
}
}
list.Add(CreateCharInfo(id, x, y, w, h, xo, yo, xadvance));
return true;
}
private bool ReadTextKerning(int idx, ref string line, ref List<CharacterInfo> list)
{
if (!line.StartsWith("kerning")) return false;
string[] keys;
string[] values;
SplitParts(line, out keys, out values);
Kerning kerning = new Kerning();
for (int i = keys.Length - 1; i >= 0; i--)
{
switch (keys[i])
{
case "first": kerning.first = int.Parse(values[i]); break;
case "second": kerning.second = int.Parse(values[i]); break;
case "amount": kerning.amount = int.Parse(values[i]); break;
}
}
kernings[idx] = kerning;
return true;
}
private bool SplitParts(string line, out string[] keys, out string[] values)
{
MatchCollection parts = pattern.Matches(line);
int count = parts.Count;
keys = new string[count - 1];
values = new string[count - 1];
for (int i = count - 2; i >= 0; i--)
{
string part = parts[i + 1].Value;
int pos = part.IndexOf('=');
keys[i] = part.Substring(0, pos);
values[i] = part.Substring(pos + 1).Trim('"');
}
return true;
}
#endregion
private CharacterInfo CreateCharInfo(int id, int x, int y, int w, int h, int xo, int yo, int xadvance, int page = 0)
{
Rect uv = new Rect();
uv.x = (float)x / textureWidth + page;
uv.y = (float)y / textureHeight;
uv.width = (float)w / textureWidth;
uv.height = (float)h / textureHeight;
uv.y = 1f - uv.y - uv.height;
Rect vert = new Rect();
vert.x = xo;
#if UNITY_5_0 || UNITY_5_1 || UNITY_5_2
// unity 5.0 can not support baseline for
vert.y = yo;
#else
vert.y = yo - lineBaseHeight;
#endif
vert.width = w;
vert.height = h;
vert.y = -vert.y;
vert.height = -vert.height;
CharacterInfo charInfo = new CharacterInfo();
charInfo.index = id;
#if UNITY_5_3_OR_NEWER || UNITY_5_3 || UNITY_5_2
charInfo.uvBottomLeft = new Vector2(uv.xMin, uv.yMin);
charInfo.uvBottomRight = new Vector2(uv.xMax, uv.yMin);
charInfo.uvTopLeft = new Vector2(uv.xMin, uv.yMax);
charInfo.uvTopRight = new Vector2(uv.xMax, uv.yMax);
charInfo.minX = (int)vert.xMin;
charInfo.maxX = (int)vert.xMax;
charInfo.minY = (int)vert.yMax;
charInfo.maxY = (int)vert.yMin;
charInfo.bearing = (int)vert.x;
charInfo.advance = xadvance;
#else
#pragma warning disable 618
charInfo.uv = uv;
charInfo.vert = vert;
charInfo.width = xadvance;
#pragma warning restore 618
#endif
return charInfo;
}
}
将这两个文件直接拖入Unity中,系统自动生成字体文件
直接使用