1、背景
??馬上要做戰(zhàn)斗跳字了蜀变,提前調(diào)研下美術(shù)字的制作悄谐。
??使用美術(shù)字可以實(shí)現(xiàn)很好表現(xiàn)效果,并獲得不錯(cuò)的性能库北,比如:規(guī)避掉描邊爬舰、Shadow的消耗们陆。
- 字體紋理創(chuàng)建使用BMFont
- CustomFont創(chuàng)建參考:Unity的UGUI中使用CustomFont(BMFont)
- BMFont使用參考:BMFONT 字體制作
美術(shù)字效果
2、BMFont使用
??參考文章中寫的已經(jīng)很詳細(xì)了情屹,這里僅列舉幾個(gè)不易發(fā)現(xiàn)的地方備忘坪仇。
支持中文
[Options - Font settings] Charset處,勾選Unicode(雖然它是默認(rèn)選中的)垃你。設(shè)置導(dǎo)出紋理格式
[Options - Export options] Textures處椅文,可以指定導(dǎo)出紋理為PNG。
在物理存儲(chǔ)上PNG比Tga小很多惜颇,而且在Unity中導(dǎo)入的效率也會(huì)更高皆刺。-
查看字符碼
界面上選中字符,在右下角的數(shù)字中m:n凌摄,m就是字符碼
字符碼 -
導(dǎo)入字符的Icon
這里只想說羡蛾,它這按鈕做的太不明顯了Orz
導(dǎo)入Icon
3、創(chuàng)建Unity的CustomFont
3.1 BMFontTool
??先給出工具的完整代碼锨亏,要說明的點(diǎn)后面慢慢寫痴怨。
public class BMFontTool
{
public static Regex BMCharMatch =
new Regex(
@"char id=(?<id>\d+)\s+x=(?<x>\d+)\s+y=(?<y>\d+)\s+width=(?<width>\d+)\s+height=(?<height>\d+)\s+xoffset=(?<xoffset>\d+)\s+yoffset=(?<yoffset>\d+)\s+xadvance=(?<xadvance>\d+)\s+");
public static Regex BMInfoMatch =
new Regex(@"common lineHeight=(?<lineHeight>\d+)\s+.*scaleW=(?<scaleW>\d+)\s+scaleH=(?<scaleH>\d+)");
public const string BMFontExt = ".fnt";
public const string FontExt = ".fontsettings";
public static string GetConfPath()
{
Object obj = Selection.activeObject;
string cfgPath = AssetDatabase.GetAssetPath(obj);
if (!cfgPath.EndsWith(".fnt"))
{
Debug.LogError("請(qǐng)選擇.fnt文件!屯伞!");
return null;
}
return cfgPath;
}
[MenuItem("Assets/Create Font")]
public static void CreateFromBMFont()
{
string cfgPath = GetConfPath();
if (null == cfgPath) return;
string name = Path.GetFileNameWithoutExtension(cfgPath);
int lineHeight = 1;
// 創(chuàng)建材質(zhì)
Material mat = new Material(Shader.Find("UI/Unlit/Text Detail"))
{
name = name,
mainTexture = AssetDatabase.LoadAssetAtPath<Texture>(cfgPath.Replace(BMFontExt, ".png")),
};
mat.SetTexture("_DetailTex", mat.mainTexture);
// 創(chuàng)建字體
Font customFont = new Font(name)
{
material = mat,
characterInfo = ParseBMFont(cfgPath, ref lineHeight).ToArray(),
};
// 修改行高
SerializedObject serializedFont = new SerializedObject(customFont);
SetLineHeight(serializedFont, lineHeight);
serializedFont.ApplyModifiedProperties();
// 保存
AssetDatabase.CreateAsset(mat, cfgPath.Replace(BMFontExt, ".mat"));
AssetDatabase.CreateAsset(customFont, cfgPath.Replace(BMFontExt, FontExt));
}
[MenuItem("Assets/Build Font")]
public static void BuildFromBMFont()
{
string cfgPath = GetConfPath();
if (null == cfgPath) return;
string fontPath = cfgPath.Replace(BMFontExt, FontExt);
if (!File.Exists(fontPath)) return;
Font customFont = AssetDatabase.LoadAssetAtPath<Font>(fontPath);
int lineHeight = 1;
List<CharacterInfo> chars = ParseBMFont(cfgPath, ref lineHeight);
SerializeFont(customFont, chars, lineHeight);
Debug.Log("字體更新完成", customFont);
}
public static List<CharacterInfo> ParseBMFont(string path, ref int lineHeight)
{
List<CharacterInfo> chars = new List<CharacterInfo>();
using (StreamReader reader = new StreamReader(path))
{
// 文字貼圖的寬腿箩、高
float texWidth = 1;
float texHeight = 1;
string line = reader.ReadLine();
while (line != null)
{
if (line.StartsWith("char id="))
{
Match match = BMCharMatch.Match(line);
if (match != Match.Empty)
{
int id = Convert.ToInt32(match.Groups["id"].Value);
int x = Convert.ToInt32(match.Groups["x"].Value);
int y = Convert.ToInt32(match.Groups["y"].Value);
int width = Convert.ToInt32(match.Groups["width"].Value);
int height = Convert.ToInt32(match.Groups["height"].Value);
int xoffset = Convert.ToInt32(match.Groups["xoffset"].Value);
int yoffset = Convert.ToInt32(match.Groups["yoffset"].Value);
int xadvance = Convert.ToInt32(match.Groups["xadvance"].Value);
// 轉(zhuǎn)換為Unity UV坐標(biāo)
float uvMinX = x / texWidth;
float uvMaxX = (x + width) / texWidth;
float uvMaxY = 1 - (y / texHeight);
float uvMinY = (texHeight - height - y) / texHeight;
// Unity字體UV的是 [左下(0, 0) - 右上(1, 1)]
// BMFont的UV是 [左上(0,0) - 右下(1, 1)]
CharacterInfo info = new CharacterInfo
{
// 字符的Unicode值
index = id,
uvBottomLeft = new Vector2(uvMinX, uvMinY),
uvBottomRight = new Vector2(uvMaxX, uvMinY),
uvTopLeft = new Vector2(uvMinX, uvMaxY),
uvTopRight = new Vector2(uvMaxX, uvMaxY),
minX = xoffset,
minY = -height / 2, // 居中對(duì)齊
glyphWidth = width,
glyphHeight = height,
// The horizontal distance from the origin of this character to the origin of the next character.
advance = xadvance,
};
chars.Add(info);
}
}
else if (line.IndexOf("scaleW=", StringComparison.Ordinal) != -1)
{
Match match = BMInfoMatch.Match(line);
if (match != Match.Empty)
{
lineHeight = Convert.ToInt32(match.Groups["lineHeight"].Value);
texWidth = Convert.ToInt32(match.Groups["scaleW"].Value);
texHeight = Convert.ToInt32(match.Groups["scaleH"].Value);
}
}
line = reader.ReadLine();
}
}
return chars;
}
public static void SetLineHeight(SerializedObject font, float height)
{
font.FindProperty("m_LineSpacing").floatValue = height;
}
/// <summary>
/// 序列化自定義字體
/// </summary>
/// <param name="font">字體資源</param>
/// <param name="chars">全部字符信息</param>
/// <param name="lineHeight">顯示的行高</param>
public static SerializedObject SerializeFont(Font font, List<CharacterInfo> chars, float lineHeight)
{
SerializedObject serializedFont = new SerializedObject(font);
SetLineHeight(serializedFont, lineHeight);
SerializeFontCharInfos(serializedFont, chars);
serializedFont.ApplyModifiedProperties();
return serializedFont;
}
/// <summary>
/// 序列化字體中的全部字符信息
/// </summary>
public static void SerializeFontCharInfos(SerializedObject font, List<CharacterInfo> chars)
{
SerializedProperty charRects = font.FindProperty("m_CharacterRects");
charRects.arraySize = chars.Count;
for (int i = 0; i < chars.Count; ++i)
{
CharacterInfo info = chars[i];
SerializedProperty prop = charRects.GetArrayElementAtIndex(i);
SerializeCharInfo(prop, info);
}
}
/// <summary>
/// 序列化一個(gè)字符信息
/// </summary>
public static void SerializeCharInfo(SerializedProperty prop, CharacterInfo charInfo)
{
prop.FindPropertyRelative("index").intValue = charInfo.index;
prop.FindPropertyRelative("uv").rectValue = charInfo.uv;
prop.FindPropertyRelative("vert").rectValue = charInfo.vert;
prop.FindPropertyRelative("advance").floatValue = charInfo.advance;
prop.FindPropertyRelative("flipped").boolValue = false;
}
}
3.2 細(xì)節(jié)說明
??上面代碼的核心部分豪直,來自于參考文章劣摇,當(dāng)然也有自己的補(bǔ)充和修改內(nèi)容。
3.2.1 解決代碼生成的CustomFont信息會(huì)丟失問題
??參考文章實(shí)現(xiàn)了創(chuàng)建CustomFont的功能弓乙,但是需要手動(dòng)保存(Ctrl+S)末融,否則在關(guān)閉Unity或切換場(chǎng)景后,字體中的信息就丟失了暇韧。
丟失的原因是勾习,直接修改Load出來的Font對(duì)象,并不會(huì)將修改的內(nèi)容序列化保存懈玻。這里我使用SerializedObject對(duì)字體進(jìn)行修改巧婶,它可以通過ApplyModifiedProperties更新序列化的內(nèi)容。
另外代碼中使用new Font() + AssetDatabase.CreateAsset()
也可以保留序列化信息涂乌,但它只能用在創(chuàng)建字體時(shí)艺栈。更新字體用它會(huì)刪掉舊字體,并創(chuàng)建一個(gè)新的湾盒,那么項(xiàng)目中用到舊字體的地方就會(huì)Missing湿右。
3.2.2 文字無法換行
??上圖的現(xiàn)象是沒有設(shè)置行高導(dǎo)致的。Inspector中行高的參數(shù)是Line Spacing罚勾,而在代碼中是Font.lineHeight毅人。這里需要注意lineHeight是只讀屬性吭狡,想在代碼里設(shè)置行高,只能借助SerializedObject丈莺。
3.2.3 豎直方向上的文字對(duì)齊
??代碼中保證了豎直居中對(duì)齊是正確的划煮,但是上對(duì)齊、下對(duì)齊的位置會(huì)出現(xiàn)問題缔俄。我探索出了在三種對(duì)齊方式下分別正確的參數(shù)般此,但是不能同時(shí)滿足三種對(duì)齊 Orz...
minY或maxY只需要設(shè)置一個(gè)即可
- 上對(duì)齊 :
minY = -height/2 - yoffset*2
- 居中對(duì)齊:
minY = -height / 2
- 下對(duì)齊∏O帧:
maxY = height-yoffset
??雖然從參數(shù)上不能同時(shí)適配三種對(duì)齊铐懊,但勾選Text組件的Align By Geometry選項(xiàng)可以解決問題。
如果有朋友解決了這個(gè)問題瞎疼,請(qǐng)不吝賜教科乎。還有minX、minY贼急、maxX茅茂、maxY這幾個(gè)參數(shù)的含義我也沒搞懂,文檔的信息實(shí)在是不清不楚 ……了解的朋友也請(qǐng)留言太抓。