我們項目從某個時候開始ui突然開始出現字體花屏現象(unity 開發版本:5.3.6p6),而且很難必現卻又時有發生,確實查找和解決起來並不太容易。
關於這個問題,uwa官方給出了解釋,http://blog.uwa4d.com/archives/techsharing_35.html, http://blog.uwa4d.com/search/%E5%AD%97%E4%BD%93/。可用的方案就是一開始把字體擴容到足夠大,但對於常用漢字就有3000多個,再加上不同的大小和樣式,不同的字體,內存占用光想一想就很可觀,所以將此當做最保底的方案。Unity 官方應該是解決過這個問題,但是沒有徹底。但是我更加高度懷疑的是:FontTexture 本身的生成和對應的 UV 信息並沒有問題,而是字體使用的 UV 有問題(沒有更新),uwa 提供的方案可能是針對的早期問題,也許現在問題是新引起的。
后來又看到雨松MOMO的博客也有提到並給出了解決方案:http://www.xuanyusong.com/archives/4259,這個方法很好,在 FontTexture 重建時在其后的 LateUpdate 中刷新所有的 Text,這個方案也是我希望的方式,不會產生過多的浪費字體紋理。不過 GameObject.FindObjectsOfType<Text>() 這個調用我覺得對於 Text 過多的場景恐怕效率堪憂,翻閱了下 UI 的源碼,發現其中有個類 UnityEngine.UI.FontUpdateTracker 就是專門用來更新 Text 相關字體信息的,但是接口都不是 public,但是可以反射調用就好,所以可以用此對 雨松MONO 的方案優化一下:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class DynamicFontTextureRebuildTracker : MonoBehaviour { private class FontUpdateNode { private bool m_FontTextureRebuilt = false; private Font m_FontRebuilt = null; public FontUpdateNode(Font font) { m_FontRebuilt = font; Validate(); } public void Validate() { if (null == m_FontRebuilt) { m_FontTextureRebuilt = false; Debug.LogWarning("You need a actual font to validate!"); return; } m_FontTextureRebuilt = true; } public void Invalidate() { m_FontTextureRebuilt = false; } public bool NeedUpdate { get { return m_FontTextureRebuilt && (null != m_FontRebuilt); } } public Font font { get { return m_FontRebuilt; } } } private System.Reflection.MethodInfo m_RebuildForFont = null; private List<FontUpdateNode> m_FontUpdateList = new List<FontUpdateNode>(); private static DynamicFontTextureRebuildTracker m_Instance = null; void Awake() { if (null != m_Instance) { Debug.LogError("There is only one DynamicFontTextureRebuildTracker instance allowed!"); Destroy(gameObject); return; } m_Instance = this; } // Use this for initialization void Start() { Font.textureRebuilt += OnFontTextureRebuilt; System.Type fontUpdateTrackerType = typeof(UnityEngine.UI.FontUpdateTracker); m_RebuildForFont = fontUpdateTrackerType.GetMethod("RebuildForFont", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); Debug.Log("Get RebuildForFont method is: " + m_RebuildForFont); } // Update is called once per frame void LateUpdate() { if (null == m_RebuildForFont) { return; } for (int i = 0; i < m_FontUpdateList.Count; i++) { FontUpdateNode node = m_FontUpdateList[i]; if (node.NeedUpdate) { Font font = node.font; m_RebuildForFont.Invoke(null, new object[] { font }); // Log rebuild. Texture fontTexture = font.material.mainTexture; Debug.Log(string.Format("Texture of dynamic font \"{0}\" is enlarged to {1}x{2}.", font.name, fontTexture.width, fontTexture.height)); node.Invalidate(); } } } void OnDestroy() { Font.textureRebuilt -= OnFontTextureRebuilt; } private void OnFontTextureRebuilt(Font font) { bool findThisFont = false; for (int i = 0; i < m_FontUpdateList.Count; i++) { FontUpdateNode node = m_FontUpdateList[i]; if (node.font == font) { node.Validate(); findThisFont = true; break; } } if (!findThisFont) { m_FontUpdateList.Add(new FontUpdateNode(font)); } } //void OnGUI() //{ // if (GUI.Button(new Rect(30.0f, 50.0f, 200.0f, 50.0f), "Force Update Text")) // { // for (int i = 0; i < m_FontUpdateList.Count; i++) // { // Font font = m_FontUpdateList[i].font; // m_RebuildForFont.Invoke(null, new object[] { font }); // Debug.Log(string.Format("Force rebuild text for font \"{0}\".", font.name)); // } // Debug.Log("Force rebuild all text ok!"); // } //} }
為了驗證確實是 FontTexture 是 ok 的而亂碼只是 Text 的 uv 不正確,可以將 OnGUI 的代碼放開,將 LateUpdate 的代碼注釋,然后運行游戲,在出現字體亂碼后點擊 “Force Update Text” 按鈕,如果文字亂碼立即消失,就證實了上面的猜測僅僅是 Text UV 沒有更新而已。經過在手機上實際測試出現亂碼后立即點擊按鈕,文字均顯示正常,也證實了這個問題。
使用時在初始場景中新建一個空 GameObject,然后 DontDestroyOnLoad,再掛上 DynamicFontTextureRebuildTracker 腳本即可。后來游戲中(ios, android)就再也沒有出現過字體花屏亂碼等現象。
最后附上該腳本下載地址:http://pan.baidu.com/s/1c1LPoJY
2017-07-01 更新:
最近我們項目的 Unity 版本一直穩定在 5.3.7p4,但是極少數情況下(甚至是某一小段時間內)依然偶爾出現了字體花屏,但是最近又沒有了,不知道原因,有可能其它系統引起了問題。
2017-07-06 更新:
最近測試同學報告說,最近偶爾依然出現的字體花屏不像以前一進場景就花掉,而是出現了角色單位后就變化了,進一步縮小了范圍,先記錄下,后面有時間研究下。