Unity制作自定義字體的兩種方法
Unity支持自定義圖片字體(CustomFont),網(wǎng)上有很多教程,細(xì)節(jié)不盡相同,當(dāng)概括起來基本就是兩種方式。一是使用BMFont,導(dǎo)出圖集和.fnt文件,再使用圖集在Unity中設(shè)置得到字體。二是不用BMFont,使用Unity自帶的Sprite類似圖集的功能。兩種方式原理相同,只是手段有區(qū)別?;驹矶际窍扔幸粡堎N圖,比如:
需要知道的信息是貼圖中每一個(gè)字符對(duì)應(yīng)的ASCII碼(例如0的ASCII碼為48)與該字符在圖集中對(duì)應(yīng)的位置(0為x:0;y:0;w:55;h:76)。然后在Unity中創(chuàng)建材質(zhì)和CustomFont并根據(jù)信息進(jìn)行設(shè)置。
最后得到字體。
兩種方式的區(qū)別僅在于第一步中如何得到圖集的信息。具體的:
對(duì)于第一種使用BMFont的方式,目的是得到.fnt文件,實(shí)際上是xml格式文件。具體的信息為:
BMFont的使用方法不再詳述。得到圖集個(gè)fnt文件后,網(wǎng)上一般的方法是手動(dòng)計(jì)算在Unity中的參數(shù),有些繁瑣,在這里寫一個(gè)Editor腳本來自動(dòng)完成這個(gè)過程。直接上代碼:
using System; using System.Collections.Generic; using System.IO; using System.Xml; using UnityEditor; using UnityEngine; public class CreateFontFromFnt : EditorWindow { [MenuItem("Tools/創(chuàng)建字體(Fnt)")] static void DoIt() { GetWindow<CreateFontFromFnt>("創(chuàng)建字體"); } private string fontName; private string fontPath; private Texture2D tex; private string fntFilePath; private void OnGUI() { GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); GUILayout.Label("字體名稱:"); fontName = EditorGUILayout.TextField(fontName); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("字體圖片:"); tex = (Texture2D)EditorGUILayout.ObjectField(tex, typeof(Texture2D), true); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button(string.IsNullOrEmpty(fontPath) ? "選擇路徑" : fontPath)) { fontPath = EditorUtility.OpenFolderPanel("字體路徑", Application.dataPath, ""); if (string.IsNullOrEmpty(fontPath)) { Debug.Log("取消選擇路徑"); } else { fontPath = fontPath.Replace(Application.dataPath, "") + "/"; } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button(string.IsNullOrEmpty(fntFilePath) ? "選擇fnt文件" : fntFilePath)) { fntFilePath = EditorUtility.OpenFilePanelWithFilters("選擇fnt文件", Environment.GetFolderPath(Environment.SpecialFolder.Desktop), new string[] { "", "fnt" }); if (string.IsNullOrEmpty(fntFilePath)) { Debug.Log("取消選擇路徑"); } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button("創(chuàng)建")) { Create(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void Create() { if (string.IsNullOrEmpty(fntFilePath)) { Debug.LogError("fnt為空"); return; } if (tex == null) { Debug.LogError("字體圖片為空"); return; } string fontSettingPath = fontPath + fontName + ".fontsettings"; string matPath = fontPath + fontName + ".mat"; if (File.Exists(Application.dataPath + fontSettingPath)) { Debug.LogErrorFormat("已存在同名字體文件:{0}", fontSettingPath); return; } if (File.Exists(Application.dataPath + matPath)) { Debug.LogErrorFormat("已存在同名字體材質(zhì):{0}", matPath); return; } var list = new List<CharacterInfo>(); XmlDocument xmlDoc = new XmlDocument(); var content = File.ReadAllText(fntFilePath, System.Text.Encoding.UTF8); xmlDoc.LoadXml(content); var nodelist = xmlDoc.SelectNodes("font/chars/char"); foreach (XmlElement item in nodelist) { CharacterInfo info = new CharacterInfo(); var id = int.Parse(item.GetAttribute("id")); var x = float.Parse(item.GetAttribute("x")); var y = float.Parse(item.GetAttribute("y")); var width = float.Parse(item.GetAttribute("width")); var height = float.Parse(item.GetAttribute("height")); info.index = id; //紋理映射,上下翻轉(zhuǎn) info.uvBottomLeft = new Vector2(x / tex.width, 1 - (y + height) / tex.height); info.uvBottomRight = new Vector2((x + width) / tex.width, 1 - (y + height) / tex.height); info.uvTopLeft = new Vector2(x / tex.width, 1 - y / tex.height); info.uvTopRight = new Vector2((x + width) / tex.width, 1 - y / tex.height); info.minX = 0; info.maxX = (int)width; info.minY = -(int)height / 2; info.maxY = (int)height / 2; info.advance = (int)width; list.Add(info); } Material mat = new Material(Shader.Find("GUI/Text Shader")); mat.SetTexture("_MainTex", tex); Font m_myFont = new Font(); m_myFont.material = mat; AssetDatabase.CreateAsset(mat, "Assets" + matPath); AssetDatabase.CreateAsset(m_myFont, "Assets" + fontSettingPath); m_myFont.characterInfo = list.ToArray(); EditorUtility.SetDirty(m_myFont); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("創(chuàng)建成功!"); } }
使用起來很簡單:
代碼也沒什么可深究的,目的是代替手動(dòng)計(jì)算,只是在紋理映射的時(shí)候有一個(gè)小坑。
第二種方式使用Unity中的Sprite。Unity支持把一個(gè)Sprite切割成多個(gè)??梢杂眠@種方式代替BMFont導(dǎo)出的fnt文件。需要手動(dòng)做的工作是將圖集的TextureType設(shè)置為Sprite,然后把SpriteMode設(shè)為Multiple,打開SpriteEditor,對(duì)圖片進(jìn)行切割。Slice就基本可以完成這個(gè)工作,如果需要再手動(dòng)微調(diào)一下。
一張圖按照字符的位置分割成了10個(gè)Sprite。然后選中每一個(gè)Sprite把Name設(shè)置成字符對(duì)應(yīng)的ASCII碼。這樣第一種方法里fnt文件包含的信息基本都包含在這個(gè)“圖集”里了。同樣寫一個(gè)Editor腳本把這些信息寫入到CustomFont里面,并不用手動(dòng)去創(chuàng)建。
using UnityEngine; using UnityEditor; using System.IO; public class CreateFont : EditorWindow { [MenuItem("Tools/創(chuàng)建字體(sprite)")] public static void Open() { GetWindow<CreateFont>("創(chuàng)建字體"); } private Texture2D tex; private string fontName; private string fontPath; private void OnGUI() { GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); GUILayout.Label("字體圖片:"); tex = (Texture2D)EditorGUILayout.ObjectField(tex, typeof(Texture2D), true); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("字體名稱:"); fontName = EditorGUILayout.TextField(fontName); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button(string.IsNullOrEmpty(fontPath) ? "選擇路徑" : fontPath)) { fontPath = EditorUtility.OpenFolderPanel("字體路徑", Application.dataPath, ""); if (string.IsNullOrEmpty(fontPath)) { Debug.Log("取消選擇路徑"); } else { fontPath = fontPath.Replace(Application.dataPath, "") + "/"; } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button("創(chuàng)建")) { Create(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void Create() { if (tex == null) { Debug.LogWarning("創(chuàng)建失敗,圖片為空!"); return; } if (string.IsNullOrEmpty(fontPath)) { Debug.LogWarning("字體路徑為空!"); return; } if (fontName == null) { Debug.LogWarning("創(chuàng)建失敗,字體名稱為空!"); return; } else { if (File.Exists(Application.dataPath + fontPath + fontName + ".fontsettings")) { Debug.LogError("創(chuàng)建失敗,已存在同名字體文件"); return; } if (File.Exists(Application.dataPath + fontPath + fontName + ".mat")) { Debug.LogError("創(chuàng)建失敗,已存在同名字體材質(zhì)文件"); return; } } string selectionPath = AssetDatabase.GetAssetPath(tex); if (selectionPath.Contains("/Resources/")) { string selectionExt = Path.GetExtension(selectionPath); if (selectionExt.Length == 0) { Debug.LogError("創(chuàng)建失?。?); return; } string fontPathName = fontPath + fontName + ".fontsettings"; string matPathName = fontPath + fontName + ".mat"; float lineSpace = 0.1f; //string loadPath = selectionPath.Remove(selectionPath.Length - selectionExt.Length).Replace("Assets/Resources/", ""); string loadPath = selectionPath.Replace(selectionExt, "").Substring(selectionPath.IndexOf("/Resources/") + "/Resources/".Length); Sprite[] sprites = Resources.LoadAll<Sprite>(loadPath); if (sprites.Length > 0) { Material mat = new Material(Shader.Find("GUI/Text Shader")); mat.SetTexture("_MainTex", tex); Font m_myFont = new Font(); m_myFont.material = mat; CharacterInfo[] characterInfo = new CharacterInfo[sprites.Length]; for (int i = 0; i < sprites.Length; i++) { if (sprites[i].rect.height > lineSpace) { lineSpace = sprites[i].rect.height; } } for (int i = 0; i < sprites.Length; i++) { Sprite spr = sprites[i]; CharacterInfo info = new CharacterInfo(); try { info.index = System.Convert.ToInt32(spr.name); } catch { Debug.LogError("創(chuàng)建失敗,Sprite名稱錯(cuò)誤!"); return; } Rect rect = spr.rect; float pivot = spr.pivot.y / rect.height - 0.5f; if (pivot > 0) { pivot = -lineSpace / 2 - spr.pivot.y; } else if (pivot < 0) { pivot = -lineSpace / 2 + rect.height - spr.pivot.y; } else { pivot = -lineSpace / 2; } int offsetY = (int)(pivot + (lineSpace - rect.height) / 2); info.uvBottomLeft = new Vector2((float)rect.x / tex.width, (float)(rect.y) / tex.height); info.uvBottomRight = new Vector2((float)(rect.x + rect.width) / tex.width, (float)(rect.y) / tex.height); info.uvTopLeft = new Vector2((float)rect.x / tex.width, (float)(rect.y + rect.height) / tex.height); info.uvTopRight = new Vector2((float)(rect.x + rect.width) / tex.width, (float)(rect.y + rect.height) / tex.height); info.minX = 0; info.minY = -(int)rect.height - offsetY; info.maxX = (int)rect.width; info.maxY = -offsetY; info.advance = (int)rect.width; characterInfo[i] = info; } AssetDatabase.CreateAsset(mat, "Assets" + matPathName); AssetDatabase.CreateAsset(m_myFont, "Assets" + fontPathName); m_myFont.characterInfo = characterInfo; EditorUtility.SetDirty(m_myFont); AssetDatabase.SaveAssets(); AssetDatabase.Refresh();//刷新資源 Debug.Log("創(chuàng)建字體成功"); } else { Debug.LogError("圖集錯(cuò)誤!"); } } else { Debug.LogError("創(chuàng)建失敗,選擇的圖片不在Resources文件夾內(nèi)!"); } } }
這個(gè)腳本參考了某一篇博文,時(shí)間長了實(shí)在是找不到了。
原理跟第一種方法一樣,只是計(jì)算細(xì)節(jié)略有差異。使用起來還是很簡單:
大同小異的兩種方法,個(gè)人更喜歡用第二種。不需要使用額外的軟件,一鍵搞定,基本上可以丟給美術(shù)童鞋來做了。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#中判斷本地系統(tǒng)的網(wǎng)絡(luò)連接狀態(tài)的方法
C#中一般通過InternetGetConnectedState函數(shù)返回本地系統(tǒng)的網(wǎng)絡(luò)連接狀態(tài),下面簡單介紹下,需要的朋友可以參考下2013-10-10C#在線程中訪問ui元素的幾種實(shí)現(xiàn)方法
在C#中,特別是在Windows窗體(WinForms)或WPF應(yīng)用程序中,直接從非UI線程(如后臺(tái)工作線程)訪問UI元素通常是不被允許的,如果你需要在非UI線程中更新UI元素,本文給大家介紹了C#在線程中訪問ui元素的幾種實(shí)現(xiàn)方法,需要的朋友可以參考下2024-07-07C#使用泛型隊(duì)列Queue實(shí)現(xiàn)生產(chǎn)消費(fèi)模式
這篇文章介紹了C#使用泛型隊(duì)列Queue實(shí)現(xiàn)生產(chǎn)消費(fèi)模式的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10C#中Dictionary與List的用法區(qū)別以及聯(lián)系詳解
List和Dictionary想必是我們平常用到最多的C#容器了,他們使用起來都很簡單,這篇文章主要給大家介紹了關(guān)于C#中Dictionary與List的用法區(qū)別以及聯(lián)系的相關(guān)資料,需要的朋友可以參考下2023-11-11C#簡易圖片格式轉(zhuǎn)換器實(shí)現(xiàn)方法
這篇文章主要介紹了C#簡易圖片格式轉(zhuǎn)換器實(shí)現(xiàn)方法,涉及C#基于WinForm操作圖片的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11C#調(diào)用barTender打印標(biāo)簽示例的實(shí)現(xiàn)
Bartender是最優(yōu)秀的條碼打印軟件,在企業(yè)里使用非常普遍,本文主要介紹了C#調(diào)用barTender打印標(biāo)簽示例的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08