一、需求

游戏开发中经常遇到需要以美术字(而非字库)做数字显示的情况,通常美术会提供一组包含单个数字(也会有其它字符)的图片,可能是一张整图,也可能是每个数字分开的散图。

在此我以一张整图这种情况为例,来说明美术字体的具体制作流程。整图如下:

二、准备

整个制作过程需要用到三样工具:

  • 字体数据制作工具
  • 图片切割工具
  • 字体生成工具

1、字体数据制作工具

字体数据制作工具名为BMFont,是一个Windows上的可执行软件,下载网址为:http://www.angelcode.com/products/bmfont/

  

  这里选择下载64位运行版(单体文件,无需安装)

  可也以点这里下载:BMFont64.exe

2、图片切割工具

图片切割工具是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的菜单项,选中图片然后右键也可以看到。

3、字体生成工具

字体生成工具也是Unity3d中一个第三方插件,名字也是BMFont(不知道和第一个软件有什么关联)。原本是NGUI中的一个字体制作工具,现被大佬剥离出来,在UGUI中也可以使用。

下载地址:BMFont字体生成工具

解压到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 fasterDictionary<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 glyphBMGlyph glyph = null;if (mDict.Count == 0){// Populate the dictionary for faster accessfor (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 trueif (!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_FLASHList<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 valuesif (comparer(buffer[i], buffer[i + 1]) > 0){// Swap the valuesT 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 timestart = (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_FLASHtry { val = int.Parse(text); } catch (System.Exception) { }
#elseint.TryParse(text, out val);
#endifreturn 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=15int 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=4if (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 glyphpublic int y;        // Offset from the top of the texture to the top of the glyphpublic int width;    // Glyph's width in pixelspublic int height;    // Glyph's height in pixelspublic int offsetX;    // Offset to apply to the cursor's left position before drawing this glyphpublic int offsetY; // Offset to apply to the cursor's top position before drawing this glyphpublic int advance;    // How much to move the cursor after printing this characterpublic 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);}
#endifreturn 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 themselvessb.Append((char)byte0);}else if ((byte0 & 224) == 192){// If an UCS fits 11 bits, it is coded as 110xxxxx 10xxxxxxif (++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 10xxxxxxif (++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();
#elsereturn 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 charactersif (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_FLASHstring[] split = line.Split(separator, System.StringSplitOptions.RemoveEmptyEntries);
#elsestring[] split = line.Split(separator, 2, System.StringSplitOptions.RemoveEmptyEntries);
#endifif (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

三、开始制作

1、切割图片

在字体数据制作软件BMFont64中,需要使用单个数字的图片,而我这个是一张包含所有数字和字母符号的整图,就需要切成单张散图。

a) 把图片导入Unity,Sprite Mode选择Multiple模式,勾选Read/Write Enable选项。见下图:

然后点击Sprite Editor进行多图区域编辑,如下图。可以先按给定的三种方式进行划分,自己再做细微调整。注意每个字符边距不要太大,不然做成字体后显示起来就会很离散。

分割完成后,点击Apply保存操作。

b) 选中图片右键,执行ImageSlicer/Process to Sprites菜单,会生成一个与图片同名的目录,里边放着切割好的散图。见下图,

2、制作字体数据

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美术字体的制作与使用

3、生成字体

a)将上述两个文件导入到Unity中,在资源面板中鼠标右键,选择Create/MaterialCreate/Custom Font菜单,

创建一个空的材质ArtNum_mat和一个空的自定义字体ArtNum(后缀为.fontsettings,在Unity中不显示),如下图:

b) 点击Tools/BMFont Maker菜单,在打开的窗口中,选择相应的文件进行赋值,如下图,

最后点击Create BMFont按钮,这样一个美术字体就生成了。

点击字体文件,能在Inspector面板的Character Rects中看到字体的映射信息。

c) 创建一个Text,输入一些数字字母和符号,字体选择为ArtNum,颜色选为白色,就能看到实际的效果。

美术字体制作完成。

后记

使用这种字体的一些小问题
1、字体不会换行,超出宽度的字体将会重叠显示,需要预留出宽度。

2、字体不受Font Size的影响,无法动态调整大小,如有需要,可通过设置Scale来解决。

3、如遇到字体信息在重启Unity后丢失的情况,可在BMFontEditor.cs脚本中的最后添加 EditorUtility.SetDirty(targetFont);来解决。

转载于:https://www.cnblogs.com/imteach/p/10743725.html

Unity3d之-使用BMFont制作美术字体相关推荐

  1. BMFont制作美术字体

    生成 Number.fnt.Number_0.png 两个文件,将其拖入Unity 相应位置,继续下一步 箭头所指就是我们要得到的最终目标,在文本处字体使用它就可以了. 在使用 Tools -> ...

  2. UGUI使用BMFont制作美术字体二

    还不多说,先来效果图: 是不是感觉比第一篇文章的工具更加高大上些,好了,现在开始讲开发流程,首先我们用到的工具依旧是BMFont,它的命令行在这里:bmfont.exe -c %1 -o %2 命令说 ...

  3. Egret 使用Texture Merger制作美术字体

    在游戏中,有时候美术为了显示更好的字体效果,美术会自己制作字体,让程序显示出来,如何实现显示美术给的美术字体效果呢.程序可以使用美术导出图片,自己使用代码组合起来使用(效率低下,程序繁琐).或者是使用 ...

  4. unity 使用BMFont 制作自定义字体

    unity cocos 使用BMFont,生成自定义字体 1.BMFont下载地址 http://www.angelcode.com/products/bmfont/ 本篇讲解如何利用美工提供的字符图 ...

  5. 基于Cocos Studio和BMfont制作艺术字体

    通常在游戏开发制作的过程中,我们常常要用到各式各样的艺术字体,适合游戏风格的.独特的艺术字体能让游戏看起来更加美观.更加生动活泼.但问题是很多新手并不知道这些字体是怎么来的,这篇文章主要讲一下如何基于 ...

  6. unity 3d中使用BMFont制作清晰字体

    1.大家可以在这免费下载到我们今天的主角工具BMFOnt. 下载BMFont 2.我们把它安装好后打开它,就是个样子了. 3.我们现在来制作我们想要的一些文字,这里我用来测试.新建一个txt文本文档, ...

  7. Unity制作美术字体、图片转字体库C#

    前言:在遇到Unity支持的字体库之外的字体时,可以将每个字的图片集中生成一个字体库,用来Unity文本使用. 一.功能 1.字体图片设置 字体图片等比等分在一张图片上,方便Unity系统自处理 2. ...

  8. unity中美术字体的制作

    unity中美术字体的制作 本次总结:客户端制作美术字体.我已知的两种方式:1.FontSetter插件.2.BMFont工具.这里比较推荐第一种插件,方便.快捷.简单. FontSetter插件 F ...

  9. Unity自定义美术字体(图片字体fontsettings)

    目录 本文内容 效果 工具代码 制作美术字体 1 资源 1.1 资源方式 1.2 资源命名 1.3 资源命名配置 2 打包字体 2.1 创建字体 2.2 更新字体 3 字体使用 本文内容 开发过程有个 ...

最新文章

  1. 求数的绝对值一定是正数_「口袋数学」绝对值的几何意义探究及应用,培优课程...
  2. RESTful协议【软件架构】
  3. thymeleaf引入css js写法
  4. 兼容所有浏览器的CSS3圆角效果
  5. 处理 read_csv 报错 OSError:Initializing from file failed
  6. 超线程_超线程加核显 i310100+梅捷H410超值爆款组合
  7. 【Leetcode | 11】268. 缺失数字
  8. sqoop动态分区导入mysql,使用sqoop import从mysql往hive含分区表中导入数据的一些注意事项...
  9. Git使用中关于rebase 、stash 、reflog、reset的一些注意点
  10. 谷歌大脑联手Hinton提出SimCLR新框架,疯狂提升自监督学习性能
  11. [ES6] 细化ES6之 -- Class关键字
  12. cation,validation,qualification有何区别
  13. linux下mysql 8 忘记密码
  14. Windows Server 2003至Windows Server 2008的迁移
  15. java的string的intern_java String的intern()方法
  16. 小升初数学知识体系梳理
  17. SAP PI/PO 视频,自己录制
  18. SpringBoot2整合ElasticSearch(包含ElasticSearch入门+spring-boot-starter-data-elasticsearch)
  19. 《西游记》八十一难的顺序
  20. 基于simulink的PN码相关峰同步仿真

热门文章

  1. 自己封装的数据库DbUtils的万能模板
  2. 小程序webview关注公众号_微信小程序和公众号互相跳转
  3. ASP.NET上传一个文件夹
  4. 输入快递单号查询不到物流怎么办
  5. java计算机毕业设计基于安卓Android的二手交易app-闲置物品交易app-ssm
  6. 电脑解锁后黑屏有鼠标_电脑开机黑屏就剩鼠标怎么回事?
  7. 全容灾、热备份、温归档、智融合,电信运营商的数据保护黄金法则
  8. ios王者荣耀服务器维护31号,王者荣耀iOS还在维护怎么回事 3月31日王者荣耀IOS什么时候能上...
  9. 小提琴统计图_小提琴图解读 统计学
  10. C++设计模式 命令模式(服务员命令厨师)