在unity的开发过程中,为了实现更好的表现,经常要使用美术字体,记录下美术字体的制作流程。
首先,美术的同学会给一张制作好的美术字的图片,当然如果能直接给fnt文件就更省事了。

拿到图片后,将图片的spriteMode设置为Multiple,然后Sprite Editor进行图片区域的划分,把图片分成宽度高度一致的小图,

分享一个在unity中使用的图片切割的工具脚本:
/**
* UnityVersion: 2018.3.10f1
* FileName: ImageSlicer.cs
* Author: TYQ
* CreateTime: 2019/04/19 00:04:26
* Description:
*/
/*
* Author:
* Date:2019/01/30 10:24:22
* Desc:图集切割器 (针对Multiple格式的图片)
* 操作方式:选中图片,选择编辑器的 Assets/ImageSlicer/Process to Sprites菜单
*/
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
public static class ImageSlicer
{
[MenuItem("Assets/ImageSlicer/Process to Sprites")]
static void ProcessToSprite()
{
Texture2D image = Selection.activeObject as Texture2D;//获取旋转的对象
string rootPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(image));//获取路径名称
string path = rootPath + "/" + image.name + ".PNG";//图片路径名称
TextureImporter texImp = AssetImporter.GetAtPath(path) as TextureImporter;//获取图片入口
AssetDatabase.CreateFolder(rootPath, image.name);//创建文件夹
foreach (SpriteMetaData metaData in texImp.spritesheet)//遍历小图集
{
Texture2D myimage = new Texture2D((int)metaData.rect.width, (int)metaData.rect.height);
//abc_0:(x:2.00, y:400.00, width:103.00, height:112.00)
for (int y = (int)metaData.rect.y; y < metaData.rect.y + metaData.rect.height; y++)//Y轴像素
{
for (int x = (int)metaData.rect.x; x < metaData.rect.x + metaData.rect.width; x++)
myimage.SetPixel(x - (int)metaData.rect.x, y - (int)metaData.rect.y, image.GetPixel(x, y));
}
//转换纹理到EncodeToPNG兼容格式
if (myimage.format != TextureFormat.ARGB32 && myimage.format != TextureFormat.RGB24)
{
Texture2D newTexture = new Texture2D(myimage.width, myimage.height);
newTexture.SetPixels(myimage.GetPixels(0), 0);
myimage = newTexture;
}
var pngData = myimage.EncodeToPNG();
//AssetDatabase.CreateAsset(myimage, rootPath + "/" + image.name + "/" + metaData.name + ".PNG");
File.WriteAllBytes(rootPath + "/" + image.name + "/" + metaData.name + ".PNG", pngData);
// 刷新资源窗口界面
AssetDatabase.Refresh();
}
}
}然后在同目录就能拿到切割好的小图了,这个图片可以在BMFont字体工具中导出fnt文件。
接下来,下载BMFont,BMFont下载地址:http://www.angelcode.com/products/bmfont/

字符ID:选中字符后可以看到字符id
点击Edit/openImage,可以选择导入图片,编辑图片,删除图片等。
字符编码可以直接查询,地址为:http://www.mytju.com/classcode/tools/encode_utf8.asp
在options中可以预览,导出设置,导出字体等,最后可以导出一张图片和一个fnt文件,这个文件在unity中不能直接使用,要转成fontsettings
在unity中新建一个CustonFont,新建一个材质,然后使用编辑器导出工具BMFontMaker进行字体的生成

将对应的4个文件填入,点生成字体,美术字就能使用了,这个编辑的拓展的代码如下:
//----------------------------------------------
// 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
}//----------------------------------------------
// 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);
}
}
}
}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");
Debug.Log("create font <" + targetFont.name + "> success");
Close();
}
EditorUtility.SetDirty(targetFont);
}
}//----------------------------------------------
// 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", "");
}
}
}
}
}
}//----------------------------------------------
// 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;
}
}//----------------------------------------------
// 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;
}
}
版权声明:本文为qq_35711014原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。