Unity瑣碎(3) UGUI 圖文混排解決方案和優化

感受使用Unity以後總能看到各類各樣解決混排的方案,只能說明Unity不夠體恤下情啊。這篇文章主要講一下我的在使用過程當中方案選擇和優化過程,已作記錄。順便提下,開源不少意味着坑,仍是要開實際需求。html

1. 方案選擇

1 TextMeshPro
Unity 最近公佈收購了TextMeshPro而且免費開源給你們使用,估計還須要幾個小版本纔會徹底融合到Unity中或者保持如今的狀態。TextMeshPro支持效果豐富,兼容如今UI層級等,性能也能夠知足移動端,可是很糾結的是:git

  • 如今的版本生成的字體庫實在太大了,比較全的漢字字庫生成TextMeshPro須要的字庫以後已經接近17M,若是考慮到遊戲中存在2種字體,估計會超過20M的常駐內存在移動端,這個至少如今還很難接受。
  • 另一個文字是序列化,20M的序列化數據,移動端受IO限制,讀取時間會有點長。這個時間長度我沒仔細測試過

基於上面亮點,最後我仍是沒有采用這種方案,若是Unity考慮融合進來,建議修改下字庫的使用方式。github

2 文字和圖片獨立渲染編程

  • 文字和圖片採用layout的方式控制渲染位置,最後會生成大量的Text和Sprite,實時計算位置信息,好比:RichText,這裏面最大的問題可能會再CPU端形成沒必要要的浪費。
  • 文字中留空間,圖片再這個空間單獨渲染。Text支持富文本的時候 會控制間隔,利用這個間隔提供圖片位置信息,而後單獨渲染圖片位置。這種方案Text不須要實時更新,圖片(帶有動態)須要實時過更新。能夠 參考這裏
  • Shader中渲染圖片:Uwa UGUI表情系統解決方案 直接再shader中渲染圖片,這個方案對於outline、shadow時避免圖片也被處理的問題沒想到好的方案,就放棄了。

最後採用了文字富文本保留空間,圖片根據位置單獨渲染的方案,主要的緣由在於性能可控以及如今代碼還算比較完善(這裏徹底是個坑)。這個版本最初的源碼:https://code.csdn.net/qq992817263/uguitextpro/tree/master緩存

2. 基本原理

2.1 基本思想數據結構

  • 利用Text富文本佔位符爲圖片保留位置、圖片名字、長寬等信息,經過字符解析獲取圖片相關信息
  • 監聽Text重繪以及位置更新等事件,並更新圖片位置

參考文章
Unity Text 插入圖片,這篇文章是基本的實現方式,後面CSDN「神碼編程」也就在這基礎上作了幾處擴展和一些文章分享編輯器

神碼編程 Unity UGUI 圖文混排系列文章性能

2.2 代碼實現思路測試

  • 提早生成sprite區域信息,若是是一個系列的表情則根據sprite名字進行區分,固然後面也根據名字進行保留和查找。如angry_0\angry_1\angry_2\angry_3 , die_0/die_1/die_2/die_4/die_5/die_6字體

  • 繼承Text組件,重寫OnPopulateMesh以及字符解析,維護裏面圖片位置、頂點等信息

  • 表情管理器:記錄全部Text中圖片(有效的)位置、紋理、頂點信息的索引關係,由數據變化時生成須要的Mesh信息並提交

  • SpriteAsset 管理器:管理圖片中全部Text中使用的圖片資源加載以及sprite位置、名字信息。

3. 爬坑記錄

最初的源碼看似可用,可是在手機端ListView滾動狀況下直接掉到20幀一下,即便在靜態100個表情同時更新的境況下效率也很難使人滿意。因此.................差很少用了一週時間爬各類坑,下面是一些主要的記錄:

3.1 優化內容

(1) GC

mark
代碼中在解析字符中基本每次都在new數據,包括解析字符、計算圖片位置、更新圖片Mesh等都存在很嚴重的GC,看上圖就能夠看到滾動中若是頻繁建立的問題。

優化思路:

  • 對於每一個Text,限制最大圖片數量以及相關結構數量,只有在不夠的時候再進行分配(不超過最大數量則),後續使用中再也不進行分配,固然增長了數據有效性判斷而不是是否爲空。
  • 對於圖片管理Mesh,則管理器中圖片總數量提早建立,只有再發生變化時纔會從新進行內存分配。如今使用的策略還需優化。
(2) 圖片信息查找

啓動時讀取配置信息,並簡歷sprite名字和信息的對應Dictionary,加快查找。固然也能夠直接以Dictionary結構進行序列化,就能夠節省這部分空間和時間,待優化。

(3) 有效圖片更新方式

原始版本中有效Sprite 列表時經過List的形式進行管理,每次任一個Text的變化(enabled,posotion等)都會將這個列表清除並從新將有效Text中的有效Sprite添加到列表中來。這種方式若是在相似ListView等一直會變化的組件中就會產生沒必要要的CPU開銷。

優化思路:

  • 維護一個有效Text的Dictionary,保存Text中對應Sprite的Key值,在Text OnEnable/OnDisable中進行註冊和註銷操做
  • 維護一個有效Sprite的Dictionary,保存Sprite string以及實際信息。
  • 每次有Text改變時只修改Sprite 鍵值表中對應的部分,固然也考慮Text註銷等狀況。

這種方式避免在頻繁更新中沒必要要的列表清除操做以及對SpriteManager lateUpdate的影響

(4) 圖片Mesh數據更新過程時間

最初的版本採用對SpriteList遍歷的形式逐個將triangles、uv、vertices 賦值到新建立的緩存中,再扔給iMesh去提交。在ListView快速移動時這部分的時間佔用就很誇張了。
mark

優化思路:

  • 儘可能減小無效sprite進入列表,限制每一個Text中sprite的最大數量
  • 採用Array.Copy的形式替代逐個賦值
(5) 佔位符亂碼清除方式

原始版本可能時做者計算錯誤了,清除亂碼的UV位置其實只須要向後4個便可,可是也原始版本是按4 * Length(標籤長度)來計算,這項的CPU佔用率特別高。
mark

(6) 動態表情更新方式

原始版本時在SpriteUpdate中每隔固定時間更新表情的索引(若是有)並從新更新Sprite Mesh內容。會產生一個問題:每種類型表情動畫圖片的數目不同,那就很難保證每一個動態表情都很天然的播放。提升更新的間隔意味着有些表情像發飆同樣

優化思路:
每類型的表情中單獨存放其時間間隔以及已經運行的時間,在Update中根據各自的狀況進行更新。

(7)圖片位置更新方式

原始代碼中是在Text :SetVerticesDirty()中進行ParseText的操做並依賴SpriteManager中LaterUpdate更新圖片的Mesh數據,產生的問題:

  • SetVerticesDirty 是Text 任何變化都會調用接口,意味着ParseText的操做在ListView滾動過程當中一直在進行。
    mark
  • SpriteManager中LaterUpdate更新與Text位置變化不一樣步,滾動時很明顯的能夠看到sprite的位置偏移

優化思路:

  • ParseText只在text文本內容變化時進行更新,可經過重載Text的text屬性實現
  • 在ListView滾動過程當中 sprite變化的只有位置信息,因此只更新位置便可,而且直接更新MESH,不等待SpriteManager。
(8)其餘

對應的還有編輯器、數據結構、貼圖資源管理等的優化

3.2 新增功能

(1)支持簡化標籤

支持 "[xxxxx]"來替代 冗長的設置

(2)圖片層級管理

方便單個Canvas下多個層級,讓Text 能夠直接設置SpriteManager或者找最近的一個。

(3)增長文本與圖片間隔設置

mark

3.3 待優化內容

(1)下劃線解析和超連接解析都是基於字符位置對應實際字符頂點位置
(2)字符串解析
(3)圖片Mesh
(4)多張sprite Asset

3.4 優化效果

測試方式,屏幕中160個動畫表情的狀況,在ListView中快速滾動下進行測試的性能曲線(主要時CPU);
優化前
mark
優化後
mark
原生Text, 有佔位符,無表情
mark

4 小結

採用這種方案各類緣由都有,有好處也有弊端,就像層級問題,解決起來會有點頭痛。通過一段時間優化勉強能夠在移動端知足需求,不過還有不少能夠繼續優化的空間。

GITHUB工程文件:https://github.com/carlosCn/Unity-EmojiText
百度網盤資源:http://pan.baidu.com/s/1geZuVNd

歡迎繼續補充。

相關文章
相關標籤/搜索