本文參考了這片博客文章,在此基礎上進行優化和改進:
https://blog.csdn.net/akof1314/article/details/80868869
先截張效果圖:
TextMeshPro在之前的博客中有介紹:
https://www.cnblogs.com/koshio0219/p/11643268.html
思來想去,這東西還是有些使用不方便的地方,問題的根本還是在於中文字庫太多,雖然縮減為7000簡體字庫或3500簡體字庫可以解決問題。
但無論怎么說,游戲中大量的字其實是沒有用到的,這勢必會造成資源浪費。
於是,接下來的想法也就應運而生——為什不能找到游戲中所有用到的字,只將這些字渲染進紋理圖呢,有新增的字就更新下字庫和紋理圖就好了。
這也就是上面這個工具誕生的最主要原因,它主要為了實現:
1.批量查找游戲中Canvas或其他GameObject上的文字內容
2.掃描查找指定路徑下的配置文件中文本內容
3.將這些文本去除重復字符后保存到一個固定的輸出路徑
4.根據輸出的游戲文本內容按照想要生成的TMP字體類型批量一鍵生產和更新
5.隨時批量修改Canvas上的字體資源
下面是一些相對具體的思路:
查找Canvas中的文字資源很簡單,只需要遍歷所有的對應組件上的內容就行了:
1 string newText = ""; 2 foreach (var targetCanvas in targetCanvasList) 3 { 4 TextMeshProUGUI[] textMeshProUGUIs = targetCanvas.GetComponentsInChildren<TextMeshProUGUI>(true); 5 foreach (var item in textMeshProUGUIs) 6 { 7 newText += item.text; 8 } 9}
對於配置文件,需要在指定的文件夾路徑中查找:
1 private void FindTextAssets(string textAssetPath) 2 { 3 textAssetList.Clear(); 4 if (Directory.Exists(textAssetPath)) 5 { 6 DirectoryInfo info = new DirectoryInfo(textAssetPath); 7 FileInfo[] files = info.GetFiles("*", SearchOption.AllDirectories); 8 9 for (int i = 0; i < files.Length; i++) 10 { 11 //去除meat文件 12 if (!files[i].Name.EndsWith(".csv.meat")) 13 { 14 var str = File.ReadAllText(files[i].ToString()); 15 Debug.Log(str); 16 textAssetList.Add(str); 17 } 18 } 19 } 20 }
對於得到的文字,每次更新時進行字符去重,去空格,去換行:
1 private string StrCutRepeat(string oriStr) 2 { 3 return string.Join("", oriStr.ToArray().Distinct().ToArray()).Replace("\n", "").Replace(" ", ""); 4 }
上面的需要用到以下命名空間:
using System.IO; using System.Linq;
每次更新完文本內容需要刷新資源:
AssetDatabase.Refresh();
清理文本資源:
1 private void ClearTextAsset(string path) 2 { 3 if (File.Exists(path)) 4 { 5 File.WriteAllText(path, "X"); 6 AssetDatabase.Refresh(); 7 } 8 }
單個TMP字體紋理的生成功能在插件中已經有了,這里只需要實現選擇控制和按順序批量生產就可以了。
在Updata()中檢測上一個字體資源的生成進度,按百分比顯示,當生成完成時循環生成下一個即可:
1 private void MyUpdate() 2 { 3 if (m_IsRepaintNeeded) 4 { 5 m_IsRepaintNeeded = false; 6 Repaint(); 7 } 8 9 // 第一步創建字體渲染數組 10 if (m_IsProcessing) 11 { 12 m_AtlasGenerationProgress = FontEngine.generationProgress; 13 m_FontAssetInfos[m_CurGenerateIndex].genPercent = m_AtlasGenerationProgress * 100; 14 15 m_IsRepaintNeeded = true; 16 } 17 18 // 是否生成完 19 if (m_IsRenderingDone) 20 { 21 m_IsProcessing = false; 22 m_IsRenderingDone = false; 23 24 if (m_IsGenerationCancelled == false) 25 { 26 // 第二步輸出渲染結果 27 UpdateRenderFeedbackWindow(); 28 // 第三步將渲染數組填充到紋理貼圖(注意,貼圖共享不刪除) 29 CreateFontTexture(); 30 foreach (var asset in m_FontAssetInfos[m_CurGenerateIndex].assets) 31 { 32 //保存信息到字體資產 33 Save_SDF_FontAsset(asset); 34 } 35 // 最后置空 36 m_FontAtlasTexture = null; 37 } 38 Repaint(); 39 //開始循環生成下一個資源 40 GenerateNext(); 41 } 42 else if (m_LoadFontFaceInMainThread == 1) 43 { 44 FontEngineError errorCode = FontEngine.LoadFontFace(m_LoadFontFaceInMainThreadFontPath); 45 m_LoadFontFaceInMainThread = errorCode == FontEngineError.Success ? 2 : 3; 46 } 47 }
1 private void GenerateNext() 2 { 3 m_CurGenerateIndex++; 4 //判斷是否所有資源序列已經執行完 5 if (m_CurGenerateIndex >= m_FontAssetInfos.Count) 6 { 7 EditorUtility.DisplayDialog("提示", "生成字庫資產成功!", "OK"); 8 return; 9 } 10 11 //判斷資源信息的生成開關是否已經開啟 12 var info = m_FontAssetInfos[m_CurGenerateIndex]; 13 if (!info.toggle) 14 { 15 GenerateNext(); 16 return; 17 } 18 19 //得到資源路徑下的文件 20 m_SourceFontFile = AssetDatabase.LoadAssetAtPath<Font>(info.fontPath); 21 //具體生成流程 22 GenerateFontAtlasButton(); 23 }
批量賦值字體資源:
1 private void AttachFontAsset() 2 { 3 foreach (var targetCanvas in attachCanvasList) 4 { 5 TextMeshProUGUI[] textMeshProUGUIs = targetCanvas.GetComponentsInChildren<TextMeshProUGUI>(true); 6 foreach (var item in textMeshProUGUIs) 7 { 8 item.font = tmpFontAsset; 9 } 10 } 11 }
Editor窗口顯示部分:
大部分的Editor顯示功能都是非常基礎的,這里記錄下幾個不太常用的功能的實現:
1.顯示需要序列化的屬性,例如要序列化List:
先定義兩個屬性,一個是需要序列化的屬性,另一個是該序列化屬性對應的對象,如下:
[SerializeField] List<GameObject> targetCanvasList = new List<GameObject>(); SerializedProperty _canvasPropertyt;
在OnEnable()中進行序列化對象的初始化和對應屬性查找賦值:
1 public void OnEnable() 2 { 3 _serializedObject = new SerializedObject(this); 4 _canvasPropertyt = _serializedObject.FindProperty("targetCanvasList"); 5 }
在繪制函數中對序列化對象進行修改更新的檢測,並隨時提交修改,例如:
1 public void OnGUI() 2 { 3 _serializedObject.Update(); 4 5 EditorGUI.BeginChangeCheck(); 6 7 EditorGUILayout.PropertyField(_canvasPropertyt, true); 8 9 if (EditorGUI.EndChangeCheck()) 10 _serializedObject.ApplyModifiedProperties(); 11 }
注意在函數EditorGUILayout.PropertyField中第二個參數一定要賦值為true,不然序列化屬性的子對象無法顯示。
2.點擊控制標題——折疊式開關的開啟與關閉:
1 public void OnGUI() 2 { 3 bShowFontWorld = EditorGUILayout.Foldout(bShowFontWorld, "Font Word", true, EditorStyles.foldoutPreDrop); 4 5 if (bShowFontWorld) 6 { 7 //折疊開關開啟時需要顯示的內容 8 } 9 }
函數EditorGUILayout.Foldout會自動在顯示內容左側創建小三角Icon,四個參數意義分別是:
1.控制點擊時是否顯示折疊的內容,為true時顯示折疊內容
2.具體的顯示內容標題
3.控制開關的檢測區域是否包含標題內容區域本身,還是只包含開關Icon部分,為true時全部包含
4.風格設置,這里的風格最好選擇帶有foldout類型的,不然無法自動創建小三角Icon
函數返回當前用戶的操作——是否開啟折疊內容。
3.Object拾取器:
tmpFontAsset = (TMP_FontAsset)EditorGUILayout.ObjectField("Font Asset", tmpFontAsset, typeof(TMP_FontAsset),false);
最后一個參數為是否允許拾取場景中的物體,返回的是一個Object對象,需要進行強制類型轉換。