Unity 位图字体

下载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文件夹下

cs 复制代码
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;
    }
}
 
cs 复制代码
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中,系统自动生成字体文件

直接使用

相关推荐
天人合一peng7 小时前
unity 生成标记根据背景色标记变色
unity·游戏引擎
天人合一peng11 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安12 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU212 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法12 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
凡情17 小时前
android隐私合规检测
android·unity
小贺儿开发17 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader
晚枫歌F1 天前
三层时间轮的实现
网络·unity·游戏引擎
咸鱼永不翻身1 天前
Lua脚本事件检查工具
unity·lua·工具