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


感覺使用Unity之后總能看到各種各樣解決混排的方案,只能說明Unity不夠體恤下情啊。這篇文章主要講一下個人在使用過程中方案選擇和優化過程,已做記錄。順便提下,開源很多意味着坑,還是要開實際需求。

1. 方案選擇

1 TextMeshPro
Unity 最近公布收購了TextMeshPro並且免費開源給大家使用,估計還需要幾個小版本才會完全融合到Unity中或者保持現在的狀態。TextMeshPro支持效果豐富,兼容現在UI層級等,性能也可以滿足移動端,但是很糾結的是:

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

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

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

歡迎繼續補充。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM