ugui位图字体使用 - fnt生成fontsettings工具
最终效果
1) fnt文件上右击,执行命令
2) 空白位置右击,打开工具窗口生成fontsettings文件。
或者从菜单 -> Assets -> BMFont -> open BMFontTool打开
字体渲染中的几个术语
1) baseline, ascent, descent, CharacterInfo的minX, minY, maxX, maxY
2) kerning(字间距修正)
kerning定义了字母的分布情况。对于良好地规定了字距的字体,字距特性使得字母分布更为统一,阅读体验更佳。如下图所示,左侧的示例没有应用字距,而右侧使用了。
Editor工具代码
#if UNITY_EDITOR using UnityEngine; using UnityEditor; using System.IO; using System; public class BMFontTool : EditorWindow { const string MenuItemPath_Rebuild_fnt = "Assets/BMFont/Rebuild fnt"; [MenuItem(MenuItemPath_Rebuild_fnt, true)] static bool AssetMenuItemValidator_Rebuild_fnt() { if (EditorApplication.isPlaying) return false; TextAsset selected = Selection.activeObject as TextAsset; if (selected == null) return false; var filePath = AssetDatabase.GetAssetPath(selected); return filePath.EndsWith(".fnt", StringComparison.OrdinalIgnoreCase); } [MenuItem(MenuItemPath_Rebuild_fnt)] static void AssetMenuItem_Rebuild_fnt() { TextAsset selected = Selection.activeObject as TextAsset; RebuildFnt(AssetDatabase.GetAssetPath(selected), selected.text); } [MenuItem("Assets/BMFont/open BMFontTool")] static void AssetMenuItem_OpenWin() { EditorWindow.GetWindow(typeof(BMFontTool)); } private TextAsset m_FntText; void OnGUI() { EditorGUILayout.BeginVertical(); EditorGUILayout.Space(); m_FntText = (TextAsset)EditorGUILayout.ObjectField("BMFont fnt (.fnt)", m_FntText, typeof(TextAsset), false); if (GUILayout.Button("Rebuild fnt")) { if (null == m_FntText) { ShowNotification(new GUIContent("No Font Position Table file selected")); return; } RebuildFnt(AssetDatabase.GetAssetPath(m_FntText), m_FntText.text); } EditorGUILayout.EndVertical(); } private static void RebuildFnt(string fntFilePath, string fntText) { var parse = FntParse.Parse(fntText); //parse.PrintInfo(); var fntFileName = Path.GetFileNameWithoutExtension(fntFilePath); var fntFileDirPath = Path.GetDirectoryName(fntFilePath); //Debug.Log($"{fntFilePath}"); //Debug.Log($"{fntFileDirPath}"); var customFontFilePath = Path.Combine(fntFileDirPath, $"{fntFileName}.fontsettings"); Font font = AssetDatabase.LoadMainAssetAtPath(customFontFilePath) as Font; if (null == font) //第1次生成 { font = new Font(); AssetDatabase.CreateAsset(font, customFontFilePath); AssetDatabase.WriteImportSettingsIfDirty(customFontFilePath); AssetDatabase.ImportAsset(customFontFilePath); } var fontMat = AssetDatabase.LoadAssetAtPath<Material>(customFontFilePath); if (null == fontMat) { fontMat = new Material(Shader.Find("UI/Default")); fontMat.name = "Font Material"; AssetDatabase.AddObjectToAsset(fontMat, customFontFilePath); AssetDatabase.ImportAsset(customFontFilePath); // unity 5.4+ cannot refresh it immediately, must import it font.material = fontMat; } //更新字体图片 var fntImageFilePath = Path.Combine(fntFileDirPath, parse.GetImageName(0)); var fntImage = AssetDatabase.LoadMainAssetAtPath(fntImageFilePath) as Texture2D; UpdateFntImageSetting(fntImageFilePath); fontMat.mainTexture = fntImage; //字符信息 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; //Descent在基线下, 所以是负值; lineHeight=ascent距离+Descent距离 SerializedProperty sp_Descent = so.FindProperty("m_Descent"); if (sp_Descent != null) sp_Descent.floatValue = parse.lineBaseHeight - parse.lineHeight; Debug.Log($"ascent:{parse.lineBaseHeight}, descent:{parse.lineBaseHeight - parse.lineHeight}"); UpdateKernings(so, parse.kernings); var b1 = so.ApplyModifiedProperties(); var b2 = so.UpdateIfRequiredOrScript(); so.SetIsDifferentCacheDirty(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); Debug.Log($"fnt finish: {b1}, {b2}"); } private static void UpdateFntImageSetting(string fntImageFilePath) { TextureImporter texImporter = AssetImporter.GetAtPath(fntImageFilePath) as TextureImporter; //Debug.Log($"{fntImageFilePath}"); texImporter.textureType = TextureImporterType.GUI; texImporter.mipmapEnabled = false; texImporter.SaveAndReimport(); } private static void UpdateKernings(SerializedObject so, FntParse.Kerning[] kernings) { SerializedProperty sp_KerningValues = so.FindProperty("m_KerningValues"); int oldKerningsCount = sp_KerningValues.arraySize; Debug.Log($"m_KerningValues: old count: {oldKerningsCount}"); if (null == kernings || kernings.Length <= 0) { sp_KerningValues.ClearArray(); return; } for (int i = 0; i < kernings.Length; i++) { if (i >= oldKerningsCount) { sp_KerningValues.InsertArrayElementAtIndex(i); } SerializedProperty sp_Kerning = sp_KerningValues.GetArrayElementAtIndex(i); var kerning = kernings[i]; sp_Kerning.FindPropertyRelative("second").floatValue = kerning.amount; SerializedProperty sp_First = sp_Kerning.FindPropertyRelative("first"); sp_First.Next(true); //第1次要true sp_First.intValue = kerning.first; sp_First.Next(false); sp_First.intValue = kerning.second; } //旧的比新的多, 多余的删掉 for (int i = oldKerningsCount - 1; i >= kernings.Length; i--) { sp_KerningValues.DeleteArrayElementAtIndex(i); } } } #endif
注意:
fnt文件中的base值,其实就对应了ascent的那段距离
参考
GitHub - litefeel/Unity-BitmapFontImporter: An unity editor extension for bitmap font.
Tools/UnityProject/Assets/Editor/BMFont at master · Aver58/Tools · GitHub
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)