[Unity3D] BMFont 简易生成工具
对网上大佬的代码进行了易用性修改,仅WINDOWS可用。using System;using System.Collections.Generic;using System.Diagnostics;using System.IO;using System.Text;using System.Text.RegularExpressions;using System.Xml;using UnityEditor;using UnityEngine;using Debug = UnityEngine.Debug;public class BmfInfo{ public string filePath; public string fileName; public int outWidth = 256; public int outHeight = 256;}public class BMFontTools : EditorWindow{ private string configDirPath; private string configFlag = "# imported icon images"; //字体文件生成路径 private string fontDirPath; private Vector2 scrollPos; private string currentSelect = string.Empty; private struct FontInfo { //图片路径 public string ImgPath; //字符 public char charInfo; } private List<FontInfo> _fontInfoList = new List<FontInfo>(); private readonly List<BmfInfo> bmfInfoList = new List<BmfInfo>(); private int xAdvanceOffset; private void Awake() { configDirPath = Path.Combine(Application.dataPath, "../Tools/BMFont"); fontDirPath = Path.Combine(Application.dataPath, "AllRes/Digit"); InitConfigInfo(); } private void InitConfigInfo() { bmfInfoList.Clear(); List<string> pathList = new List<string>(Directory.GetFiles(configDirPath, "*.bmfc")); for (int i = 0; i < pathList.Count; i++) { BmfInfo info = new BmfInfo(); info.fileName = Path.GetFileNameWithoutExtension(pathList); info.filePath = pathList; string[] tempLines = File.ReadAllLines(info.filePath); foreach (string tempLine in tempLines) { if (tempLine.StartsWith("outWidth=")) { string infoTemp = tempLine.Split('#'); int width = int.Parse(infoTemp.Replace("outWidth=", string.Empty)); info.outWidth = width; } else if (tempLine.StartsWith("outHeight=")) { string infoTemp = tempLine.Split('#'); int height = int.Parse(infoTemp.Replace("outHeight=", string.Empty)); info.outHeight = height; } } bmfInfoList.Add(info); } if (!IsNullOrEmpty(bmfInfoList)) { currentSelect = bmfInfoList.filePath; _fontInfoList = AnalysisConfig(currentSelect); } }#if UNITY_EDITOR_WIN private static void MyBMFontTools() { string path = Path.Combine(Application.dataPath, "../Tools/BMFont/bmfont.exe"); if (!File.Exists(path)) { Debug.LogWarning($"{path}目录下不存在"); if (EditorUtility.DisplayDialog("BMFont工具不存在", $"{path}目录下不存在", "这就去下载")) { Application.OpenURL("http://www.angelcode.com/products/bmfont/"); }; return; } path = Path.Combine(Application.dataPath, "../Tools/BMFont/BMFontGenerate.bat"); if (!File.Exists(path)) { File.WriteAllText(path, "bmfont.exe -c %1-o %2"); } BMFontTools bmFont = GetWindow<BMFontTools>(); bmFont.Show(); }#endif private void OnGUI() { GUILayout.Label("使用说明:"); GUILayout.Label("1、点击NewFont创建新的字体配置。"); GUILayout.Label("2、修改FontName后,点击ReName修改字体名字。"); GUILayout.Label("3、outWidth和outHeight为输出图集宽高,如果输出多张图集,请增大图集宽高。"); GUILayout.Label("4、点击Select选中字体配置,选中后为绿色。"); GUILayout.Label("5、点击下方Add按钮,增加字符。"); GUILayout.Label("6、点击SelectImg选择图片,要求路径是全英文路径。"); GUILayout.Label("7、[可选]修改字符的X轴间隔。"); GUILayout.Label("8、点击下方SaveAndExportFont输出字体文件。"); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button("Refresh", EditorStyles.toolbarButton)) { InitConfigInfo(); } if (GUILayout.Button("NewFont", EditorStyles.toolbarButton)) { string fileName = $"{DateTime.Now:yyyyMMddhhmmss}.bmfc"; File.WriteAllText(Path.Combine(configDirPath, fileName), configFlag, Encoding.UTF8); InitConfigInfo(); currentSelect = ToFind(bmfInfoList, x => x.filePath.Contains(fileName)).filePath; _fontInfoList = AnalysisConfig(currentSelect); } if (GUILayout.Button("Delete", EditorStyles.toolbarButton)) { if (File.Exists(currentSelect)) { File.Delete(currentSelect); } _fontInfoList = new List<FontInfo>(); InitConfigInfo(); return; } GUILayout.EndHorizontal(); scrollPos = EditorGUILayout.BeginScrollView(scrollPos); EditorGUILayout.BeginVertical(); EditorGUILayout.BeginHorizontal(); GUILayout.Label("Font Save Path:", GUILayout.MaxWidth(100)); fontDirPath = GUILayout.TextField(fontDirPath); if (GUILayout.Button("SelectFold")) { fontDirPath = EditorUtility.OpenFolderPanel("SelectFontOutPutDir", "", ""); } EditorGUILayout.EndHorizontal(); SetBMFontConfigs(); SetConfigInfo(); EditorGUILayout.EndVertical(); EditorGUILayout.EndScrollView(); } private void SetBMFontConfigs() { for (int i = 0; i < bmfInfoList.Count; i++) { if (bmfInfoList.filePath.Equals(currentSelect)) { GUI.color = Color.green; } string filePath = bmfInfoList.filePath; GUILayout.BeginHorizontal(); GUI.enabled = false; EditorGUILayout.TextField(filePath, GUILayout.MaxWidth(500)); GUI.enabled = true; EditorGUILayout.LabelField("FontName:", GUILayout.MaxWidth(80)); bmfInfoList.fileName = EditorGUILayout.TextField(bmfInfoList.fileName, GUILayout.MaxWidth(100)); EditorGUILayout.LabelField("outWidth:", GUILayout.MaxWidth(70)); bmfInfoList.outWidth = int.Parse(EditorGUILayout.TextField(bmfInfoList.outWidth.ToString(), GUILayout.MaxWidth(50))); EditorGUILayout.LabelField("outHeight:", GUILayout.MaxWidth(70)); bmfInfoList.outHeight = int.Parse(EditorGUILayout.TextField(bmfInfoList.outHeight.ToString(), GUILayout.MaxWidth(50))); if (GUILayout.Button("ReName")) { Regex regex = new Regex(@"/|\\|<|>|\*|\?"); if (!string.IsNullOrEmpty(bmfInfoList.fileName) && !regex.IsMatch(bmfInfoList.fileName)) { string fileNameTemp = filePath.Replace(Path.GetFileNameWithoutExtension(filePath), bmfInfoList.fileName); if (File.Exists(fileNameTemp)) { Debug.LogError("文件冲突,命名失败"); } else { File.Move(filePath, fileNameTemp); } InitConfigInfo(); } else { Debug.LogError("文件名非法或为空,命名失败"); } } if (GUILayout.Button("Select")) { currentSelect = filePath; _fontInfoList = AnalysisConfig(currentSelect); } GUILayout.EndHorizontal(); if (filePath.Equals(currentSelect)) { GUI.color = Color.white; } } } private void SetConfigInfo() { if (!string.IsNullOrEmpty(currentSelect)) { for (int i = 0; i < _fontInfoList.Count; i++) { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Select Img", GUILayout.MaxWidth(100))) { string pathTemp = EditorUtility.OpenFilePanelWithFilters("选择图片", "", new[] { "Image", "png" }); string fileName = Path.GetFileName(pathTemp).Replace(".png", ""); if (!string.IsNullOrEmpty(pathTemp)) { FontInfo fontInfo = new FontInfo(); fontInfo.charInfo = _fontInfoList.charInfo; if (fontInfo.charInfo == '\0') { if (int.TryParse(fileName, out int rs)) { fontInfo.charInfo = rs.ToString(); } else { fontInfo.charInfo = fileName; } } fontInfo.ImgPath = FormatPath(pathTemp); _fontInfoList = fontInfo; int result = 0; if (int.TryParse(fileName, out result) || int.TryParse(fileName.ToString(), out result)) { int i2 = result; int index = i + 1; while (i2 < 10) { i2++; string path2 = pathTemp.Replace(result + ".png", i2 + ".png"); if (!string.IsNullOrEmpty(path2) && File.Exists(path2)) { FontInfo fontInfo2 = new FontInfo(); fontInfo2.charInfo = i2.ToString(); fontInfo2.ImgPath = FormatPath(path2); while (true) { if (_fontInfoList.Count <= index) { _fontInfoList.Add(fontInfo2); index++; break; } if (string.IsNullOrEmpty(_fontInfoList.ImgPath)) { _fontInfoList = fontInfo2; index++; break; } else { index++; } } } else { break; } } } } } EditorGUILayout.LabelField("Char:", GUILayout.MaxWidth(55)); if (!string.IsNullOrEmpty(_fontInfoList.charInfo.ToString())) { FontInfo info = new FontInfo(); string temp = EditorGUILayout.TextField(_fontInfoList.charInfo.ToString(), GUILayout.MaxWidth(30)); if (temp.Length == 1 && Regex.IsMatch(temp, "[\x20-\x7e]")) { info.charInfo = temp; info.ImgPath = _fontInfoList.ImgPath; _fontInfoList = info; } } EditorGUILayout.LabelField("ImgPath:", GUILayout.MaxWidth(55)); GUI.enabled = false; EditorGUILayout.TextField(_fontInfoList.ImgPath); GUI.enabled = true; if (GUILayout.Button("Delete")) { _fontInfoList.RemoveAt(i); i--; } EditorGUILayout.EndHorizontal(); } EditorGUILayout.LabelField($"当前字符数量:{_fontInfoList.Count}"); GUILayout.Space(10); EditorGUILayout.BeginHorizontal(); int.TryParse(EditorGUILayout.TextField("额外的文字间隔", xAdvanceOffset.ToString()), out xAdvanceOffset); EditorGUILayout.EndHorizontal(); if (GUILayout.Button("Add")) { _fontInfoList.Add(new FontInfo()); } GUI.enabled = !IsNullOrEmpty(_fontInfoList); GUILayout.Space(50); if (GUILayout.Button("Save And Export Font")) { SaveFontAndExport(); } GUI.enabled = true; } } private void SaveFontAndExport() { BmfInfo bmfInfo = ToFind(bmfInfoList, x => x.filePath.Equals(currentSelect)); string baseFontInfo = File.ReadAllText(configDirPath + "/BaseConfig.bmf"); baseFontInfo = baseFontInfo .Replace("outWidth=", $"outWidth={bmfInfo.outWidth}#") .Replace("outHeight=", $"outHeight={bmfInfo.outHeight}#"); baseFontInfo += "\n"; for (int i = 0; i < _fontInfoList.Count; i++) { if (string.IsNullOrEmpty(_fontInfoList.ImgPath) || string.IsNullOrEmpty(_fontInfoList.charInfo.ToString())) { continue; } string info = $"icon=\"{_fontInfoList.ImgPath}\",{(int)_fontInfoList.charInfo},0,0,0\n"; baseFontInfo += info; } File.WriteAllText(currentSelect, baseFontInfo); ExportFontInfo(); AssetDatabase.Refresh(); } private void ExportFontInfo() { string fileName = Path.GetFileNameWithoutExtension(currentSelect); string targetDir = Path.Combine(fontDirPath, fileName); if (!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); } Process process = new Process(); string batPath = Path.Combine(configDirPath, "BMFontGenerate.bat"); process.StartInfo.FileName = batPath; process.StartInfo.WorkingDirectory = configDirPath; process.StartInfo.Arguments = string.Format("{0} {1}", currentSelect, Path.Combine(fontDirPath, targetDir + "/" + fileName)); process.Start(); process.WaitForExit(); AssetDatabase.Refresh(); GenFontInfo(targetDir, fileName); } private string GetAssetPath(string path) { string pathTemp = path.Replace("\\", "/"); pathTemp = pathTemp.Replace(Application.dataPath, "Assets"); return pathTemp; } private void GenFontInfo(string fontDirPath, string fileName) { string matPath = Path.Combine(fontDirPath, fileName + "_0.mat"); Material mat = AssetDatabase.LoadAssetAtPath<Material>(GetAssetPath(matPath)); if (mat == null) { mat = new Material(Shader.Find("UI/Default Font")); AssetDatabase.CreateAsset(mat, GetAssetPath(matPath)); } string texturePath = Path.Combine(fontDirPath, fileName + "_0.png"); Texture _fontTexture = AssetDatabase.LoadAssetAtPath<Texture>(GetAssetPath(texturePath)); mat = AssetDatabase.LoadAssetAtPath<Material>(GetAssetPath(matPath)); mat.SetTexture("_MainTex", _fontTexture); string fontPath = Path.Combine(fontDirPath, fileName + ".fontsettings"); Font font = AssetDatabase.LoadAssetAtPath<Font>(GetAssetPath(fontPath)); if (font == null) { font = new Font(); AssetDatabase.CreateAsset(font, GetAssetPath(fontPath)); } string fontConfigPath = Path.Combine(fontDirPath, fileName + ".fnt"); List<CharacterInfo> chars = GetFontInfo(fontConfigPath, _fontTexture); font.material = mat; SerializeFont(font, chars, 1); //File.Delete(fontConfigPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } private List<CharacterInfo> GetFontInfo(string fontConfig, Texture texture) { XmlDocument xml = new XmlDocument(); xml.Load(fontConfig); XmlNode info = xml.GetElementsByTagName("info"); XmlNodeList chars = xml.GetElementsByTagName("chars").ChildNodes; CharacterInfo[] charInfos = new CharacterInfo; for (int cnt = 0; cnt < chars.Count; cnt++) { XmlNode node = chars; CharacterInfo charInfo = new CharacterInfo(); charInfo.index = ToInt(node, "id"); charInfo.advance = (int)ToFloat(node, "xadvance") + xAdvanceOffset; Rect r = GetUV(node, (Texture2D)texture); //这里注意下UV坐标系和从BMFont里得到的信息的坐标系是不一样的哦,前者左下角为(0,0), //右上角为(1,1)。而后者则是左上角上角为(0,0),右下角为(图宽,图高) charInfo.uvBottomLeft = new Vector2(r.xMin, r.yMin); //字符uv左下角坐标 charInfo.uvBottomRight = new Vector2(r.xMax, r.yMin); //字符uv右下角坐标 charInfo.uvTopLeft = new Vector2(r.xMin, r.yMax); //字符uv左上角坐标 charInfo.uvTopRight = new Vector2(r.xMax, r.yMax); //字符uv右上角坐标 //Debug.LogError($"charInfo.advance = {charInfo.advance}"); // uv1.x 字符的横轴占用空间 //Debug.LogError($"r.xMin = {r.xMin}"); // uv1.x uv起始点x坐标 //Debug.LogError($"r.yMin = {r.yMin}"); // uv1.y uv起始点y坐标 //Debug.LogError($"r.xMax = {r.xMax}"); // uv2.x uv终止点y坐标 //Debug.LogError($"r.yMax = {r.yMax}"); // uv2.y uv终止点y坐标 //Debug.Log("charInfo.minY = " + charInfo.minY); // 同上uv //Debug.Log("charInfo.maxY = " + charInfo.maxY); //Debug.Log("charInfo.minX = " + charInfo.minX); //Debug.Log("charInfo.maxX = " + charInfo.maxX); //Debug.Log("------------------------"); r = GetVert(node); charInfo.minX = (int)r.xMin; charInfo.maxX = (int)r.xMax; //不居中 //charInfo.minY = (int)r.yMax; //charInfo.maxY = (int)r.yMin; //居中显示 charInfo.minY = (int)((r.yMax - r.yMin) / 2); charInfo.maxY = -(int)((r.yMax - r.yMin) / 2); charInfos = charInfo; } return new List<CharacterInfo>(charInfos); } private static void SetLineHeight(SerializedObject font, float height) { font.FindProperty("m_LineSpacing").floatValue = height; } private static SerializedObject SerializeFont(Font font, List<CharacterInfo> chars, float lineHeight) { SerializedObject serializedFont = new SerializedObject(font); SetLineHeight(serializedFont, lineHeight); SerializeFontCharInfos(serializedFont, chars); serializedFont.ApplyModifiedProperties(); return serializedFont; } private static void SerializeFontCharInfos(SerializedObject font, List<CharacterInfo> chars) { SerializedProperty charRects = font.FindProperty("m_CharacterRects"); charRects.arraySize = chars.Count; for (int i = 0; i < chars.Count; ++i) { CharacterInfo info = chars; SerializedProperty prop = charRects.GetArrayElementAtIndex(i); SerializeCharInfo(prop, info); } } private static void SerializeCharInfo(SerializedProperty prop, CharacterInfo charInfo) { prop.FindPropertyRelative("index").intValue = charInfo.index; prop.FindPropertyRelative("uv").rectValue = charInfo.uv; prop.FindPropertyRelative("vert").rectValue = charInfo.vert; prop.FindPropertyRelative("advance").floatValue = charInfo.advance; prop.FindPropertyRelative("flipped").boolValue = false; } private List<FontInfo> AnalysisConfig(string configPath) { List<FontInfo> infoList = new List<FontInfo>(); string[] fileInfo = File.ReadAllLines(configPath); bool isGetInfoFlag = false; for (int i = 0; i < fileInfo.Length; i++) { if (fileInfo.Contains(configFlag) || isGetInfoFlag) { if (!isGetInfoFlag) { i++; isGetInfoFlag = true; } if (i < fileInfo.Length && !string.IsNullOrEmpty(fileInfo)) { infoList.Add(GetFontInfoByStr(fileInfo)); } } } return infoList; } private FontInfo GetFontInfoByStr(string str) { string[] strTemp = str.Split(','); FontInfo fontInfo = new FontInfo(); string strPathTemp = string.Empty; for (int i = 0; i < strTemp.Length; i++) { if (IsOddDoubleQuota(strTemp)) { strPathTemp += strTemp + ","; if (!IsOddDoubleQuota(strPathTemp)) { strPathTemp = strPathTemp.Substring(0, strPathTemp.Length - 1); break; } } else { strPathTemp = strTemp; break; } } fontInfo.ImgPath = strPathTemp.Replace("icon=\"", string.Empty).Replace("\"", string.Empty); fontInfo.charInfo = (char)int.Parse(strTemp); return fontInfo; } private bool IsOddDoubleQuota(string str) { return GetDoubleQuotaCount(str) % 2 == 1; } private int GetDoubleQuotaCount(string str) { string[] strArray = str.Split('"'); int doubleQuotaCount = strArray.Length - 1; doubleQuotaCount = doubleQuotaCount < 0 ? 0 : doubleQuotaCount; return doubleQuotaCount; } private string FormatPath(string path) { path = path.Replace("\\", "/"); return path; } private Rect GetUV(XmlNode node, Texture2D textureFile) { Rect uv = new Rect { x = ToFloat(node, "x") / textureFile.width, y = ToFloat(node, "y") / textureFile.height, width = ToFloat(node, "width") / textureFile.width, height = ToFloat(node, "height") / textureFile.height }; uv.y = 1f - uv.y - uv.height; return uv; } private Rect GetVert(XmlNode node) { Rect uv = new Rect { x = ToFloat(node, "xoffset"), y = ToFloat(node, "yoffset"), width = ToFloat(node, "width"), 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); } static bool IsNullOrEmpty<T>(List<T> lst) { if (lst == null) { return true; } if (lst.Count == 0) { return true; } return false; } static T ToFind<T>(List<T> obj, Predicate<T> predicate) { for (int i = 0; i < obj.Count; i++) { if (predicate(obj)) { return obj; } } return default; }}
页:
[1]