Unity3d之-使用BMFont製做美術字體 Unity教程之-UGUI美術字體的製做與使用

1、需求

遊戲開發中常常遇到須要以美術字(而非字庫)作數字顯示的狀況,一般美術會提供一組包含單個數字(也會有其它字符)的圖片,多是一張整圖,也多是每一個數字分開的散圖。html

在此我以一張整圖這種狀況爲例,來講明美術字體的具體制做流程。整圖以下:express

 

2、準備

整個製做過程須要用到三樣工具:app

  • 字體數據製做工具
  • 圖片切割工具
  • 字體生成工具

一、字體數據製做工具

字體數據製做工具名爲BMFont,是一個Windows上的可執行軟件,下載網址爲:http://www.angelcode.com/products/bmfont/less

  

  這裏選擇下載64位運行版(單體文件,無需安裝)編輯器

  可也以點這裏下載:BMFont64.exeide

 

二、圖片切割工具

圖片切割工具是Unity中運行的一個工具類,類名爲ImageSlicer,放在Editor目錄下便可,代碼以下:工具

 1 /**
 2 * UnityVersion: 2018.3.10f1
 3 * FileName:     ImageSlicer.cs
 4 * Author:       TYQ
 5 * CreateTime:   2019/04/19 00:04:26
 6 * Description:  
 7 */
 8 /*
 9 * Author:
10 * Date:2019/01/30 10:24:22 
11 * Desc:圖集切割器 (針對Multiple格式的圖片)
12 * 操做方式:選中圖片,選擇編輯器的 Assets/ImageSlicer/Process to Sprites菜單
13 */
14 
15 using UnityEngine;
16 using System.Collections;
17 using UnityEditor;
18 using System.IO;
19 using System.Collections.Generic;
20 
21 public static class ImageSlicer
22 {
23     [MenuItem("Assets/ImageSlicer/Process to Sprites")]
24     static void ProcessToSprite()
25     {
26         Texture2D image = Selection.activeObject as Texture2D;//獲取旋轉的對象
27         string rootPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(image));//獲取路徑名稱
28         string path = rootPath + "/" + image.name + ".PNG";//圖片路徑名稱
29 
30 
31         TextureImporter texImp = AssetImporter.GetAtPath(path) as TextureImporter;//獲取圖片入口
32 
33 
34         AssetDatabase.CreateFolder(rootPath, image.name);//建立文件夾
35 
36 
37         foreach (SpriteMetaData metaData in texImp.spritesheet)//遍歷小圖集
38         {
39             Texture2D myimage = new Texture2D((int)metaData.rect.width, (int)metaData.rect.height);
40 
41             //abc_0:(x:2.00, y:400.00, width:103.00, height:112.00)
42             for (int y = (int)metaData.rect.y; y < metaData.rect.y + metaData.rect.height; y++)//Y軸像素
43             {
44                 for (int x = (int)metaData.rect.x; x < metaData.rect.x + metaData.rect.width; x++)
45                     myimage.SetPixel(x - (int)metaData.rect.x, y - (int)metaData.rect.y, image.GetPixel(x, y));
46             }
47 
48 
49             //轉換紋理到EncodeToPNG兼容格式
50             if (myimage.format != TextureFormat.ARGB32 && myimage.format != TextureFormat.RGB24)
51             {
52                 Texture2D newTexture = new Texture2D(myimage.width, myimage.height);
53                 newTexture.SetPixels(myimage.GetPixels(0), 0);
54                 myimage = newTexture;
55             }
56             var pngData = myimage.EncodeToPNG();
57 
58 
59             //AssetDatabase.CreateAsset(myimage, rootPath + "/" + image.name + "/" + metaData.name + ".PNG");
60             File.WriteAllBytes(rootPath + "/" + image.name + "/" + metaData.name + ".PNG", pngData);
61             // 刷新資源窗口界面
62             AssetDatabase.Refresh();
63         }
64     }
65 }
ImageSlicer.cs

編譯完成後會在Assets菜單下生成一個ImageSlicer/Process to Sprites的菜單項,選中圖片而後右鍵也能夠看到。post

 三、字體生成工具

字體生成工具也是Unity3d中一個第三方插件,名字也是BMFont(不知道和第一個軟件有什麼關聯)。本來是NGUI中的一個字體制做工具,現被大佬剝離出來,在UGUI中也能夠使用。字體

 下載地址:BMFont字體生成工具ui

 解壓到Assets目錄下便可,編譯完成後,會在Unity編輯器上生成一個Tools/BMFont Maker菜單。

 

BMFont插件是源碼形式的,共包括6個文件:

//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2015 Tasharen Entertainment
//----------------------------------------------

using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// BMFont reader. C# implementation of http://www.angelcode.com/products/bmfont/
/// </summary>

[System.Serializable]
public class BMFont
{
    [HideInInspector][SerializeField] int mSize = 16;            // How much to move the cursor when moving to the next line
    [HideInInspector][SerializeField] int mBase = 0;            // Offset from the top of the line to the base of each character
    [HideInInspector][SerializeField] int mWidth = 0;            // Original width of the texture
    [HideInInspector][SerializeField] int mHeight = 0;            // Original height of the texture
    [HideInInspector][SerializeField] string mSpriteName;

    // List of serialized glyphs
    [HideInInspector][SerializeField] List<BMGlyph> mSaved = new List<BMGlyph>();

    // Actual glyphs that we'll be working with are stored in a dictionary, making the lookup faster
    Dictionary<int, BMGlyph> mDict = new Dictionary<int, BMGlyph>();

    /// <summary>
    /// Whether the font can be used.
    /// </summary>

    public bool isValid { get { return (mSaved.Count > 0); } }

    /// <summary>
    /// Size of this font (for example 32 means 32 pixels).
    /// </summary>

    public int charSize { get { return mSize; } set { mSize = value; } }

    /// <summary>
    /// Base offset applied to characters.
    /// </summary>

    public int baseOffset { get { return mBase; } set { mBase = value; } }

    /// <summary>
    /// Original width of the texture.
    /// </summary>

    public int texWidth { get { return mWidth; } set { mWidth = value; } }

    /// <summary>
    /// Original height of the texture.
    /// </summary>

    public int texHeight { get { return mHeight; } set { mHeight = value; } }

    /// <summary>
    /// Number of valid glyphs.
    /// </summary>

    public int glyphCount { get { return isValid ? mSaved.Count : 0; } }

    /// <summary>
    /// Original name of the sprite that the font is expecting to find (usually the name of the texture).
    /// </summary>

    public string spriteName { get { return mSpriteName; } set { mSpriteName = value; } }

    /// <summary>
    /// Access to BMFont's entire set of glyphs.
    /// </summary>

    public List<BMGlyph> glyphs { get { return mSaved; } }

    /// <summary>
    /// Helper function that retrieves the specified glyph, creating it if necessary.
    /// </summary>

    public BMGlyph GetGlyph (int index, bool createIfMissing)
    {
        // Get the requested glyph
        BMGlyph glyph = null;

        if (mDict.Count == 0)
        {
            // Populate the dictionary for faster access
            for (int i = 0, imax = mSaved.Count; i < imax; ++i)
            {
                BMGlyph bmg = mSaved[i];
                mDict.Add(bmg.index, bmg);
            }
        }

        // Saved check is here so that the function call is not needed if it's true
        if (!mDict.TryGetValue(index, out glyph) && createIfMissing)
        {
            glyph = new BMGlyph();
            glyph.index = index;
            mSaved.Add(glyph);
            mDict.Add(index, glyph);
        }
        return glyph;
    }

    /// <summary>
    /// Retrieve the specified glyph, if it's present.
    /// </summary>

    public BMGlyph GetGlyph (int index) { return GetGlyph(index, false); }

    /// <summary>
    /// Clear the glyphs.
    /// </summary>

    public void Clear ()
    {
        mDict.Clear();
        mSaved.Clear();
    }

    /// <summary>
    /// Trim the glyphs, ensuring that they will never go past the specified bounds.
    /// </summary>

    public void Trim (int xMin, int yMin, int xMax, int yMax)
    {
        if (isValid)
        {
            for (int i = 0, imax = mSaved.Count; i < imax; ++i)
            {
                BMGlyph glyph = mSaved[i];
                if (glyph != null) glyph.Trim(xMin, yMin, xMax, yMax);
            }
        }
    }
}
1.BMFont.cs
using UnityEngine;
using UnityEditor;

public class BMFontEditor : EditorWindow
{
    [MenuItem("Tools/BMFont Maker")]
    static public void OpenBMFontMaker()
    {
        EditorWindow.GetWindow<BMFontEditor>(false, "BMFont Maker", true).Show();
    }

    [SerializeField]
    private Font targetFont;
    [SerializeField]
    private TextAsset fntData;
    [SerializeField]
    private Material fontMaterial;
    [SerializeField]
    private Texture2D fontTexture;

    private BMFont bmFont = new BMFont();

    public BMFontEditor()
    {
    }

    void OnGUI()
    {
        targetFont = EditorGUILayout.ObjectField("Target Font", targetFont, typeof(Font), false) as Font;
        fntData = EditorGUILayout.ObjectField("Fnt Data", fntData, typeof(TextAsset), false) as TextAsset;
        fontMaterial = EditorGUILayout.ObjectField("Font Material", fontMaterial, typeof(Material), false) as Material;
        fontTexture = EditorGUILayout.ObjectField("Font Texture", fontTexture, typeof(Texture2D), false) as Texture2D;

        if (GUILayout.Button("Create BMFont"))
        {
            BMFontReader.Load(bmFont, fntData.name, fntData.bytes); // 借用NGUI封裝的讀取類
            CharacterInfo[] characterInfo = new CharacterInfo[bmFont.glyphs.Count];
            for (int i = 0; i < bmFont.glyphs.Count; i++)
            {
                BMGlyph bmInfo = bmFont.glyphs[i];
                CharacterInfo info = new CharacterInfo();
                info.index = bmInfo.index;
                info.uv.x = (float)bmInfo.x / (float)bmFont.texWidth;
                info.uv.y = 1 - (float)bmInfo.y / (float)bmFont.texHeight;
                info.uv.width = (float)bmInfo.width / (float)bmFont.texWidth;
                info.uv.height = -1f * (float)bmInfo.height / (float)bmFont.texHeight;
                info.vert.x = 0;
                info.vert.y = -(float)bmInfo.height;
                info.vert.width = (float)bmInfo.width;
                info.vert.height = (float)bmInfo.height;
                info.width = (float)bmInfo.advance;
                characterInfo[i] = info;
            }
            targetFont.characterInfo = characterInfo;
            if (fontMaterial)
            {
                fontMaterial.mainTexture = fontTexture;
            }
            targetFont.material = fontMaterial;
            fontMaterial.shader = Shader.Find("UI/Default");

            //偶遇字體信息在重啓後失丟失的狀況,須要加此句
            EditorUtility.SetDirty(targetFont);

            Debug.Log("create font <" + targetFont.name + "> success");
            Close();
        }
    }
}
2.BMFontEditor.cs
//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2015 Tasharen Entertainment
//----------------------------------------------

using UnityEngine;
using System.Collections.Generic;
using System.Diagnostics;

/// <summary>
/// This improved version of the System.Collections.Generic.List that doesn't release the buffer on Clear(),
/// resulting in better performance and less garbage collection.
/// PRO: BetterList performs faster than List when you Add and Remove items (although slower if you remove from the beginning).
/// CON: BetterList performs worse when sorting the list. If your operations involve sorting, use the standard List instead.
/// </summary>

public class BetterList<T>
{
#if UNITY_FLASH

    List<T> mList = new List<T>();
    
    /// <summary>
    /// Direct access to the buffer. Note that you should not use its 'Length' parameter, but instead use BetterList.size.
    /// </summary>
    
    public T this[int i]
    {
        get { return mList[i]; }
        set { mList[i] = value; }
    }
    
    /// <summary>
    /// Compatibility with the non-flash syntax.
    /// </summary>
    
    public List<T> buffer { get { return mList; } }

    /// <summary>
    /// Direct access to the buffer's size. Note that it's only public for speed and efficiency. You shouldn't modify it.
    /// </summary>

    public int size { get { return mList.Count; } }

    /// <summary>
    /// For 'foreach' functionality.
    /// </summary>

    public IEnumerator<T> GetEnumerator () { return mList.GetEnumerator(); }

    /// <summary>
    /// Clear the array by resetting its size to zero. Note that the memory is not actually released.
    /// </summary>

    public void Clear () { mList.Clear(); }

    /// <summary>
    /// Clear the array and release the used memory.
    /// </summary>

    public void Release () { mList.Clear(); }

    /// <summary>
    /// Add the specified item to the end of the list.
    /// </summary>

    public void Add (T item) { mList.Add(item); }

    /// <summary>
    /// Insert an item at the specified index, pushing the entries back.
    /// </summary>

    public void Insert (int index, T item)
    {
        if (index > -1 && index < mList.Count) mList.Insert(index, item);
        else mList.Add(item);
    }

    /// <summary>
    /// Returns 'true' if the specified item is within the list.
    /// </summary>

    public bool Contains (T item) { return mList.Contains(item); }

    /// <summary>
    /// Return the index of the specified item.
    /// </summary>

    public int IndexOf (T item) { return mList.IndexOf(item); }

    /// <summary>
    /// Remove the specified item from the list. Note that RemoveAt() is faster and is advisable if you already know the index.
    /// </summary>

    public bool Remove (T item) { return mList.Remove(item); }

    /// <summary>
    /// Remove an item at the specified index.
    /// </summary>

    public void RemoveAt (int index) { mList.RemoveAt(index); }

    /// <summary>
    /// Remove an item from the end.
    /// </summary>

    public T Pop ()
    {
        if (buffer != null && size != 0)
        {
            T val = buffer[mList.Count - 1];
            mList.RemoveAt(mList.Count - 1);
            return val;
        }
        return default(T);
    }

    /// <summary>
    /// Mimic List's ToArray() functionality, except that in this case the list is resized to match the current size.
    /// </summary>

    public T[] ToArray () { return mList.ToArray(); }

    /// <summary>
    /// List.Sort equivalent.
    /// </summary>

    public void Sort (System.Comparison<T> comparer) { mList.Sort(comparer); }

#else

    /// <summary>
    /// Direct access to the buffer. Note that you should not use its 'Length' parameter, but instead use BetterList.size.
    /// </summary>

    public T[] buffer;

    /// <summary>
    /// Direct access to the buffer's size. Note that it's only public for speed and efficiency. You shouldn't modify it.
    /// </summary>

    public int size = 0;

    /// <summary>
    /// For 'foreach' functionality.
    /// </summary>

    [DebuggerHidden]
    [DebuggerStepThrough]
    public IEnumerator<T> GetEnumerator ()
    {
        if (buffer != null)
        {
            for (int i = 0; i < size; ++i)
            {
                yield return buffer[i];
            }
        }
    }
    
    /// <summary>
    /// Convenience function. I recommend using .buffer instead.
    /// </summary>

    [DebuggerHidden]
    public T this[int i]
    {
        get { return buffer[i]; }
        set { buffer[i] = value; }
    }

    /// <summary>
    /// Helper function that expands the size of the array, maintaining the content.
    /// </summary>

    void AllocateMore ()
    {
        T[] newList = (buffer != null) ? new T[Mathf.Max(buffer.Length << 1, 32)] : new T[32];
        if (buffer != null && size > 0) buffer.CopyTo(newList, 0);
        buffer = newList;
    }

    /// <summary>
    /// Trim the unnecessary memory, resizing the buffer to be of 'Length' size.
    /// Call this function only if you are sure that the buffer won't need to resize anytime soon.
    /// </summary>

    void Trim ()
    {
        if (size > 0)
        {
            if (size < buffer.Length)
            {
                T[] newList = new T[size];
                for (int i = 0; i < size; ++i) newList[i] = buffer[i];
                buffer = newList;
            }
        }
        else buffer = null;
    }

    /// <summary>
    /// Clear the array by resetting its size to zero. Note that the memory is not actually released.
    /// </summary>

    public void Clear () { size = 0; }

    /// <summary>
    /// Clear the array and release the used memory.
    /// </summary>

    public void Release () { size = 0; buffer = null; }

    /// <summary>
    /// Add the specified item to the end of the list.
    /// </summary>

    public void Add (T item)
    {
        if (buffer == null || size == buffer.Length) AllocateMore();
        buffer[size++] = item;
    }

    /// <summary>
    /// Insert an item at the specified index, pushing the entries back.
    /// </summary>

    public void Insert (int index, T item)
    {
        if (buffer == null || size == buffer.Length) AllocateMore();

        if (index > -1 && index < size)
        {
            for (int i = size; i > index; --i) buffer[i] = buffer[i - 1];
            buffer[index] = item;
            ++size;
        }
        else Add(item);
    }

    /// <summary>
    /// Returns 'true' if the specified item is within the list.
    /// </summary>

    public bool Contains (T item)
    {
        if (buffer == null) return false;
        for (int i = 0; i < size; ++i) if (buffer[i].Equals(item)) return true;
        return false;
    }

    /// <summary>
    /// Return the index of the specified item.
    /// </summary>

    public int IndexOf (T item)
    {
        if (buffer == null) return -1;
        for (int i = 0; i < size; ++i) if (buffer[i].Equals(item)) return i;
        return -1;
    }

    /// <summary>
    /// Remove the specified item from the list. Note that RemoveAt() is faster and is advisable if you already know the index.
    /// </summary>

    public bool Remove (T item)
    {
        if (buffer != null)
        {
            EqualityComparer<T> comp = EqualityComparer<T>.Default;

            for (int i = 0; i < size; ++i)
            {
                if (comp.Equals(buffer[i], item))
                {
                    --size;
                    buffer[i] = default(T);
                    for (int b = i; b < size; ++b) buffer[b] = buffer[b + 1];
                    buffer[size] = default(T);
                    return true;
                }
            }
        }
        return false;
    }

    /// <summary>
    /// Remove an item at the specified index.
    /// </summary>

    public void RemoveAt (int index)
    {
        if (buffer != null && index > -1 && index < size)
        {
            --size;
            buffer[index] = default(T);
            for (int b = index; b < size; ++b) buffer[b] = buffer[b + 1];
            buffer[size] = default(T);
        }
    }

    /// <summary>
    /// Remove an item from the end.
    /// </summary>

    public T Pop ()
    {
        if (buffer != null && size != 0)
        {
            T val = buffer[--size];
            buffer[size] = default(T);
            return val;
        }
        return default(T);
    }

    /// <summary>
    /// Mimic List's ToArray() functionality, except that in this case the list is resized to match the current size.
    /// </summary>

    public T[] ToArray () { Trim(); return buffer; }

    //class Comparer : System.Collections.IComparer
    //{
    //    public System.Comparison<T> func;
    //    public int Compare (object x, object y) { return func((T)x, (T)y); }
    //}

    //Comparer mComp = new Comparer();

    /// <summary>
    /// List.Sort equivalent. Doing Array.Sort causes GC allocations.
    /// </summary>

    //public void Sort (System.Comparison<T> comparer)
    //{
    //    if (size > 0)
    //    {
    //        mComp.func = comparer;
    //        System.Array.Sort(buffer, 0, size, mComp);
    //    }
    //}

    /// <summary>
    /// List.Sort equivalent. Manual sorting causes no GC allocations.
    /// </summary>

    [DebuggerHidden]
    [DebuggerStepThrough]
    public void Sort (CompareFunc comparer)
    {
        int start = 0;
        int max = size - 1;
        bool changed = true;

        while (changed)
        {
            changed = false;

            for (int i = start; i < max; ++i)
            {
                // Compare the two values
                if (comparer(buffer[i], buffer[i + 1]) > 0)
                {
                    // Swap the values
                    T temp = buffer[i];
                    buffer[i] = buffer[i + 1];
                    buffer[i + 1] = temp;
                    changed = true;
                }
                else if (!changed)
                {
                    // Nothing has changed -- we can start here next time
                    start = (i == 0) ? 0 : i - 1;
                }
            }
        }
    }

    /// <summary>
    /// Comparison function should return -1 if left is less than right, 1 if left is greater than right, and 0 if they match.
    /// </summary>

    public delegate int CompareFunc (T left, T right);
#endif
}
3.BetterList.cs
//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2015 Tasharen Entertainment
//----------------------------------------------

using UnityEngine;
using UnityEditor;
using System.Text;

/// <summary>
/// Helper class that takes care of loading BMFont's glyph information from the specified byte array.
/// This functionality is not a part of BMFont anymore because Flash export option can't handle System.IO functions.
/// </summary>

public static class BMFontReader
{
    /// <summary>
    /// Helper function that retrieves the string value of the key=value pair.
    /// </summary>

    static string GetString (string s)
    {
        int idx = s.IndexOf('=');
        return (idx == -1) ? "" : s.Substring(idx + 1);
    }

    /// <summary>
    /// Helper function that retrieves the integer value of the key=value pair.
    /// </summary>

    static int GetInt (string s)
    {
        int val = 0;
        string text = GetString(s);
#if UNITY_FLASH
        try { val = int.Parse(text); } catch (System.Exception) { }
#else
        int.TryParse(text, out val);
#endif
        return val;
    }

    /// <summary>
    /// Reload the font data.
    /// </summary>

    static public void Load (BMFont font, string name, byte[] bytes)
    {
        font.Clear();

        if (bytes != null)
        {
            ByteReader reader = new ByteReader(bytes);
            char[] separator = new char[] { ' ' };

            while (reader.canRead)
            {
                string line = reader.ReadLine();
                if (string.IsNullOrEmpty(line)) break;
                string[] split = line.Split(separator, System.StringSplitOptions.RemoveEmptyEntries);
                int len = split.Length;

                if (split[0] == "char")
                {
                    // Expected data style:
                    // char id=13 x=506 y=62 width=3 height=3 xoffset=-1 yoffset=50 xadvance=0 page=0 chnl=15

                    int channel = (len > 10) ? GetInt(split[10]) : 15;

                    if (len > 9 && GetInt(split[9]) > 0)
                    {
                        Debug.LogError("Your font was exported with more than one texture. Only one texture is supported by NGUI.\n" +
                            "You need to re-export your font, enlarging the texture's dimensions until everything fits into just one texture.");
                        break;
                    }

                    if (len > 8)
                    {
                        int id = GetInt(split[1]);
                        BMGlyph glyph = font.GetGlyph(id, true);

                        if (glyph != null)
                        {
                            glyph.x            = GetInt(split[2]);
                            glyph.y            = GetInt(split[3]);
                            glyph.width        = GetInt(split[4]);
                            glyph.height    = GetInt(split[5]);
                            glyph.offsetX    = GetInt(split[6]);
                            glyph.offsetY    = GetInt(split[7]);
                            glyph.advance    = GetInt(split[8]);
                            glyph.channel    = channel;
                        }
                        else Debug.Log("Char: " + split[1] + " (" + id + ") is NULL");
                    }
                    else
                    {
                        Debug.LogError("Unexpected number of entries for the 'char' field (" + name + ", " + split.Length + "):\n" + line);
                        break;
                    }
                }
                else if (split[0] == "kerning")
                {
                    // Expected data style:
                    // kerning first=84 second=244 amount=-5 

                    if (len > 3)
                    {
                        int first  = GetInt(split[1]);
                        int second = GetInt(split[2]);
                        int amount = GetInt(split[3]);

                        BMGlyph glyph = font.GetGlyph(second, true);
                        if (glyph != null) glyph.SetKerning(first, amount);
                    }
                    else
                    {
                        Debug.LogError("Unexpected number of entries for the 'kerning' field (" +
                            name + ", " + split.Length + "):\n" + line);
                        break;
                    }
                }
                else if (split[0] == "common")
                {
                    // Expected data style:
                    // common lineHeight=64 base=51 scaleW=512 scaleH=512 pages=1 packed=0 alphaChnl=1 redChnl=4 greenChnl=4 blueChnl=4

                    if (len > 5)
                    {
                        font.charSize    = GetInt(split[1]);
                        font.baseOffset = GetInt(split[2]);
                        font.texWidth    = GetInt(split[3]);
                        font.texHeight    = GetInt(split[4]);

                        int pages = GetInt(split[5]);

                        if (pages != 1)
                        {
                            Debug.LogError("Font '" + name + "' must be created with only 1 texture, not " + pages);
                            break;
                        }
                    }
                    else
                    {
                        Debug.LogError("Unexpected number of entries for the 'common' field (" +
                            name + ", " + split.Length + "):\n" + line);
                        break;
                    }
                }
                else if (split[0] == "page")
                {
                    // Expected data style:
                    // page id=0 file="textureName.png"

                    if (len > 2)
                    {
                        font.spriteName = GetString(split[2]).Replace("\"", "");
                        font.spriteName = font.spriteName.Replace(".png", "");
                        font.spriteName = font.spriteName.Replace(".tga", "");
                    }
                }
            }
        }
    }
}
4.BMFontReader.cs
//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2015 Tasharen Entertainment
//----------------------------------------------

using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// Glyph structure used by BMFont. For more information see http://www.angelcode.com/products/bmfont/
/// </summary>

[System.Serializable]
public class BMGlyph
{
    public int index;    // Index of this glyph (used by BMFont)
    public int x;        // Offset from the left side of the texture to the left side of the glyph
    public int y;        // Offset from the top of the texture to the top of the glyph
    public int width;    // Glyph's width in pixels
    public int height;    // Glyph's height in pixels
    public int offsetX;    // Offset to apply to the cursor's left position before drawing this glyph
    public int offsetY; // Offset to apply to the cursor's top position before drawing this glyph
    public int advance;    // How much to move the cursor after printing this character
    public int channel;    // Channel mask (in most cases this will be 15 (RGBA, 1+2+4+8)
    public List<int> kerning;

    /// <summary>
    /// Retrieves the special amount by which to adjust the cursor position, given the specified previous character.
    /// </summary>

    public int GetKerning (int previousChar)
    {
        if (kerning != null && previousChar != 0)
        {
            for (int i = 0, imax = kerning.Count; i < imax; i += 2)
                if (kerning[i] == previousChar)
                    return kerning[i + 1];
        }
        return 0;
    }

    /// <summary>
    /// Add a new kerning entry to the character (or adjust an existing one).
    /// </summary>

    public void SetKerning (int previousChar, int amount)
    {
        if (kerning == null) kerning = new List<int>();

        for (int i = 0; i < kerning.Count; i += 2)
        {
            if (kerning[i] == previousChar)
            {
                kerning[i + 1] = amount;
                return;
            }
        }

        kerning.Add(previousChar);
        kerning.Add(amount);
    }

    /// <summary>
    /// Trim the glyph, given the specified minimum and maximum dimensions in pixels.
    /// </summary>

    public void Trim (int xMin, int yMin, int xMax, int yMax)
    {
        int x1 = x + width;
        int y1 = y + height;

        if (x < xMin)
        {
            int offset = xMin - x;
            x += offset;
            width -= offset;
            offsetX += offset;
        }

        if (y < yMin)
        {
            int offset = yMin - y;
            y += offset;
            height -= offset;
            offsetY += offset;
        }

        if (x1 > xMax) width  -= x1 - xMax;
        if (y1 > yMax) height -= y1 - yMax;
    }
}
5.BMGlyph.cs
//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2015 Tasharen Entertainment
//----------------------------------------------

using UnityEngine;
using System.Text;
using System.Collections.Generic;
using System.IO;

/// <summary>
/// MemoryStream.ReadLine has an interesting oddity: it doesn't always advance the stream's position by the correct amount:
/// http://social.msdn.microsoft.com/Forums/en-AU/Vsexpressvcs/thread/b8f7837b-e396-494e-88e1-30547fcf385f
/// Solution? Custom line reader with the added benefit of not having to use streams at all.
/// </summary>

public class ByteReader
{
    byte[] mBuffer;
    int mOffset = 0;

    public ByteReader (byte[] bytes) { mBuffer = bytes; }
    public ByteReader (TextAsset asset) { mBuffer = asset.bytes; }

    /// <summary>
    /// Read the contents of the specified file and return a Byte Reader to work with.
    /// </summary>

    static public ByteReader Open (string path)
    {
#if UNITY_EDITOR || (!UNITY_FLASH && !NETFX_CORE && !UNITY_WP8 && !UNITY_WP_8_1)
        FileStream fs = File.OpenRead(path);

        if (fs != null)
        {
            fs.Seek(0, SeekOrigin.End);
            byte[] buffer = new byte[fs.Position];
            fs.Seek(0, SeekOrigin.Begin);
            fs.Read(buffer, 0, buffer.Length);
            fs.Close();
            return new ByteReader(buffer);
        }
#endif
        return null;
    }

    /// <summary>
    /// Whether the buffer is readable.
    /// </summary>

    public bool canRead { get { return (mBuffer != null && mOffset < mBuffer.Length); } }

    /// <summary>
    /// Read a single line from the buffer.
    /// </summary>

    static string ReadLine (byte[] buffer, int start, int count)
    {
#if UNITY_FLASH
        // Encoding.UTF8 is not supported in Flash :(
        StringBuilder sb = new StringBuilder();

        int max = start + count;

        for (int i = start; i < max; ++i)
        {
            byte byte0 = buffer[i];

            if ((byte0 & 128) == 0)
            {
                // If an UCS fits 7 bits, its coded as 0xxxxxxx. This makes ASCII character represented by themselves
                sb.Append((char)byte0);
            }
            else if ((byte0 & 224) == 192)
            {
                // If an UCS fits 11 bits, it is coded as 110xxxxx 10xxxxxx
                if (++i == count) break;
                byte byte1 = buffer[i];
                int ch = (byte0 & 31) << 6;
                ch |= (byte1 & 63);
                sb.Append((char)ch);
            }
            else if ((byte0 & 240) == 224)
            {
                // If an UCS fits 16 bits, it is coded as 1110xxxx 10xxxxxx 10xxxxxx
                if (++i == count) break;
                byte byte1 = buffer[i];
                if (++i == count) break;
                byte byte2 = buffer[i];

                if (byte0 == 0xEF && byte1 == 0xBB && byte2 == 0xBF)
                {
                    // Byte Order Mark -- generally the first 3 bytes in a Windows-saved UTF-8 file. Skip it.
                }
                else
                {
                    int ch = (byte0 & 15) << 12;
                    ch |= (byte1 & 63) << 6;
                    ch |= (byte2 & 63);
                    sb.Append((char)ch);
                }
            }
            else if ((byte0 & 248) == 240)
            {
                // If an UCS fits 21 bits, it is coded as 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
                if (++i == count) break;
                byte byte1 = buffer[i];
                if (++i == count) break;
                byte byte2 = buffer[i];
                if (++i == count) break;
                byte byte3 = buffer[i];

                int ch = (byte0 & 7) << 18;
                ch |= (byte1 & 63) << 12;
                ch |= (byte2 & 63) << 6;
                ch |= (byte3 & 63);
                sb.Append((char)ch);
            }
        }
        return sb.ToString();
#else
        return Encoding.UTF8.GetString(buffer, start, count);
#endif
    }

    /// <summary>
    /// Read a single line from the buffer.
    /// </summary>

    public string ReadLine () { return ReadLine(true); }

    /// <summary>
    /// Read a single line from the buffer.
    /// </summary>

    public string ReadLine (bool skipEmptyLines)
    {
        int max = mBuffer.Length;

        // Skip empty characters
        if (skipEmptyLines)
        {
            while (mOffset < max && mBuffer[mOffset] < 32) ++mOffset;
        }

        int end = mOffset;

        if (end < max)
        {
            for (; ; )
            {
                if (end < max)
                {
                    int ch = mBuffer[end++];
                    if (ch != '\n' && ch != '\r') continue;
                }
                else ++end;

                string line = ReadLine(mBuffer, mOffset, end - mOffset - 1);
                mOffset = end;
                return line;
            }
        }
        mOffset = max;
        return null;
    }

    /// <summary>
    /// Assume that the entire file is a collection of key/value pairs.
    /// </summary>

    public Dictionary<string, string> ReadDictionary ()
    {
        Dictionary<string, string> dict = new Dictionary<string, string>();
        char[] separator = new char[] { '=' };

        while (canRead)
        {
            string line = ReadLine();
            if (line == null) break;
            if (line.StartsWith("//")) continue;

#if UNITY_FLASH
            string[] split = line.Split(separator, System.StringSplitOptions.RemoveEmptyEntries);
#else
            string[] split = line.Split(separator, 2, System.StringSplitOptions.RemoveEmptyEntries);
#endif

            if (split.Length == 2)
            {
                string key = split[0].Trim();
                string val = split[1].Trim().Replace("\\n", "\n");
                dict[key] = val;
            }
        }
        return dict;
    }

    static BetterList<string> mTemp = new BetterList<string>();

    /// <summary>
    /// Read a single line of Comma-Separated Values from the file.
    /// </summary>

    public BetterList<string> ReadCSV ()
    {
        mTemp.Clear();
        string line = "";
        bool insideQuotes = false;
        int wordStart = 0;

        while (canRead)
        {
            if (insideQuotes)
            {
                string s = ReadLine(false);
                if (s == null) return null;
                s = s.Replace("\\n", "\n");
                line += "\n" + s;
            }
            else
            {
                line = ReadLine(true);
                if (line == null) return null;
                line = line.Replace("\\n", "\n");
                wordStart = 0;
            }

            for (int i = wordStart, imax = line.Length; i < imax; ++i)
            {
                char ch = line[i];

                if (ch == ',')
                {
                    if (!insideQuotes)
                    {
                        mTemp.Add(line.Substring(wordStart, i - wordStart));
                        wordStart = i + 1;
                    }
                }
                else if (ch == '"')
                {
                    if (insideQuotes)
                    {
                        if (i + 1 >= imax)
                        {
                            mTemp.Add(line.Substring(wordStart, i - wordStart).Replace("\"\"", "\""));
                            return mTemp;
                        }

                        if (line[i + 1] != '"')
                        {
                            mTemp.Add(line.Substring(wordStart, i - wordStart).Replace("\"\"", "\""));
                            insideQuotes = false;

                            if (line[i + 1] == ',')
                            {
                                ++i;
                                wordStart = i + 1;
                            }
                        }
                        else ++i;
                    }
                    else
                    {
                        wordStart = i + 1;
                        insideQuotes = true;
                    }
                }
            }

            if (wordStart < line.Length)
            {
                if (insideQuotes) continue;
                mTemp.Add(line.Substring(wordStart, line.Length - wordStart));
            }
            return mTemp;
        }
        return null;
    }
}
6.ByteReader.cs

3、開始製做

一、切割圖片

在字體數據製做軟件BMFont64中,須要使用單個數字的圖片,而我這個是一張包含全部數字和字母符號的整圖,就須要切成單張散圖。

a) 把圖片導入Unity,Sprite Mode選擇Multiple模式,勾選Read/Write Enable選項。見下圖:

 

而後點擊Sprite Editor進行多圖區域編輯,以下圖。能夠先按給定的三種方式進行劃分,本身再作細微調整。注意每一個字符邊距不要太大,否則作成字體後顯示起來就會很離散。

 

分割完成後,點擊Apply保存操做。

 

b) 選中圖片右鍵,執行ImageSlicer/Process to Sprites菜單,會生成一個與圖片同名的目錄,裏邊放着切割好的散圖。見下圖,

 

二、製做字體數據

a) 打開BMFont64軟件,點擊Edit下的Open Image Manager菜單。

 

在打開的Image Manager窗口有一個Image菜單,能夠進行圖片導入、編輯和刪除操做。

操做方式:這裏以逗號字符爲例,鼠標放在主窗口逗號方格的位置,右下會顯示其編號,記住這個編號。

 

而後在Image Manager窗口中選擇導入圖片,選中切割成散圖的逗號圖片,在Icon Image彈窗的Id中填入逗號方格的編號:44,點擊Ok。

 

依樣導入其它的圖片,並填入Id值,最後的完成圖以下:每一個字符方格的編號,對應一個相應的圖片。

 

b) 點擊Options/Export options菜單,

 打開導出選項窗口,這裏邊主要設置一個合成圖片的寬和高,以及導出格式。

這個軟件的最後一步操做是導出字體數據,包括一個字體數據文件(.fnt格式)和一張紋理圖。這個紋理圖會把全部的單圖又合成一張。

這裏的Width是指這張合成紋理的總寬度(最比如全部圖片加起來的數值要大一點,由於每一個數字圖片合成時會有一個px的間隔),

Height是單個圖片的高度(最比如圖片高1像素以上)。

不能一次設置準確也不要緊,能夠點擊Options/Visualize菜單預覽合成效果,再微調高寬值,最終讓全部圖片都能剛剛顯示爲好。

 

導出格式格式設置爲png。(若是圖片有模糊可把Bit depth設置爲32位試試,瞎猜的,不必定有用)

合成圖預覽以下:

 

c)  點擊Options/Save bitmap font as..菜單,選擇位置後進行保存操做,最終會獲得兩個文件(ArtNum.fnt和ArtNum_0.png),以下圖:

 

字體名字能夠自由定義,導出的時候,每一個方格要處在選中狀態(淺灰色)。

關於BMFont64軟件的操做,也能夠參考文章:Unity教程之-UGUI美術字體的製做與使用

 

三、生成字體

a)將上述兩個文件導入到Unity中,在資源面板中鼠標右鍵,選擇Create/MaterialCreate/Custom Font菜單,

建立一個空的材質ArtNum_mat和一個空的自定義字體ArtNum(後綴爲.fontsettings,在Unity中不顯示),以下圖:

 

b) 點擊Tools/BMFont Maker菜單,在打開的窗口中,選擇相應的文件進行賦值,以下圖,

最後點擊Create BMFont按鈕,這樣一個美術字體就生成了。

點擊字體文件,能在Inspector面板的Character Rects中看到字體的映射信息。

 

c) 建立一個Text,輸入一些數字字母和符號,字體選擇爲ArtNum,顏色選爲白色,就能看到實際的效果。

  

美術字體制做完成。

 

後記

使用這種字體的一些小問題
一、字體不會換行,超出寬度的字體將會重疊顯示,須要預留出寬度。

二、字體不受Font Size的影響,沒法動態調整大小,若有須要,可經過設置Scale來解決。

三、如遇到字體信息在重啓Unity後丟失的狀況,可在BMFontEditor.cs腳本中的最後添加 EditorUtility.SetDirty(targetFont);來解決。

相關文章
相關標籤/搜索