找回密码
 立即注册
查看: 329|回复: 0

[Unity3D] BMFont 简易生成工具

[复制链接]
发表于 2022-7-22 12:21 | 显示全部楼层 |阅读模式
对网上大佬的代码进行了易用性修改,仅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('#')[0];                    int width = int.Parse(infoTemp.Replace("outWidth=", string.Empty));                    info.outWidth = width;                }                else if (tempLine.StartsWith("outHeight="))                {                    string infoTemp = tempLine.Split('#')[0];                    int height = int.Parse(infoTemp.Replace("outHeight=", string.Empty));                    info.outHeight = height;                }            }            bmfInfoList.Add(info);        }        if (!IsNullOrEmpty(bmfInfoList))        {            currentSelect = bmfInfoList[0].filePath;            _fontInfoList = AnalysisConfig(currentSelect);        }    }#if UNITY_EDITOR_WIN    [MenuItem("AssetsTools/BMFont/BMFontTextureTools", false)]    private static void MyBMFontTools()    {        string path = Path.Combine(Application.dataPath, "../Tools/BMFont/bmfont.exe");        if (!File.Exists(path))        {            Debug.LogWarning($"{path}目录下不存在[bmfont.exe]");            if (EditorUtility.DisplayDialog("BMFont工具不存在", $"{path}目录下不存在[bmfont.exe]", "这就去下载"))            {                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()[0];                            }                            else                            {                                fontInfo.charInfo = fileName[fileName.Length - 1];                            }                        }                        fontInfo.ImgPath = FormatPath(pathTemp);                        _fontInfoList = fontInfo;                        int result = 0;                        if (int.TryParse(fileName, out result) || int.TryParse(fileName[fileName.Length - 1].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()[0];                                    fontInfo2.ImgPath = FormatPath(path2);                                    while (true)                                    {                                        if (_fontInfoList.Count <= index)                                        {                                            _fontInfoList.Add(fontInfo2);                                            index++;                                            break;                                        }                                        if (string.IsNullOrEmpty(_fontInfoList[index].ImgPath))                                        {                                            _fontInfoList[index] = 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[0];                        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")[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.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[cnt] = 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[strTemp.Length - 4]);        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;    }}
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-15 12:26 , Processed in 0.092445 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表