RedZero9 发表于 2022-7-22 12:21

[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]
查看完整版本: [Unity3D] BMFont 简易生成工具