翻譯自:https://unity3d.com/cn/learn/tutorials/topics/best-practices/optimizing-ui-controls?playlist=30089html
這一章節關注一些特定的UI控件。大部分UI控件在性能方面是類似的,其中有兩個控件在遊戲快完成時,可能會遇到許多性能問題。算法
Unity 內置的Text組件能夠很方便的將光柵化的文本符號顯示到UI中。然而又不少不被知道的問題,會頻繁的產生性能開銷點。當你像UI中添加文本時,請時刻記住每個文字的文本符號都是一個獨立的四邊形。根據形狀的不一樣,這些四邊形可能的周圍可能會包圍着不少透明區域,並且很容易放置text組件,致使阻斷其餘能夠合批的UI元素。canvas
一個很嚴重的問題就是UI text的重建。不管什麼時候,當UI Text組件發生變化時,text 組件會從新計算實際顯示的多邊形。當一個文本的父級GameObject只是簡單的disable & re-enabled,並無改變文本時,也會致使從新計算。api
這種行爲對於那些顯示大量文字標籤的UI如排行榜和統計界面來講,是有問題的。最多見的方式去隱藏和顯示Unity UI就是enable/disable一個包含了UI的GameObject,當顯示包含大量Text組件的文本時,常常會出現的意外的幀率峯值。緩存
有關此問題的解決方法,請參閱下一章的 Disabling Canvas Renderers 章節。佈局
顯示擁有大量可顯示文字的字庫,或者運行時沒法預測的文本時,使用動態字體是一種很是便利的方式。在Unity中,這些字體根據UIText組件運行時使用到的字符,動態的建立一個字符圖集。性能
每一個加載的字體對象都包含一個本身的紋理圖集,即便它和另外一個字體有相同的font family。好比,在一個控件上使用Arial粗體,在另外一個控件上使用Arial Bold字體,它們的表現是相同的,可是Unity會保留兩張獨立的紋理貼圖——一張給Arial字體使用,一張給Arial Bold字體使用。測試
Unity UI的動態字體會在字體的紋理圖集中,每個大小,風格不一樣的文字都會保留一個字符(glyph )。這意味着,若是一個UI包含兩個Text組件,兩個都顯示字符‘A’,那麼:字體
當一個UI Text對象遇到一個尚未被字體圖集光柵化的字符,那麼字體圖集必須重建。若是新的字符適合放入到當前的圖集,它將被加入到圖集中,圖集會從新傳遞給繪製設備。然而,若是當前的圖集很小,那麼系統會嘗試去重建圖集。它分兩個階段進行。動畫
第一步,圖集會被重建成相同大小,它只包含active的UI Text組件(1)上顯示的字符。若是系統成功的將全部當前使用的字符放到一個新的圖集中,那麼它將光柵化這個圖集,而且跳過第二步。
第二步,若是當前使用的字符集合不能被放到和當前圖集相同大小的圖集中,一個是圖集短邊長度兩倍的圖集將被建立。好比512 * 215圖集擴展到512 * 1024大小的圖集。
基於上述算法,一個被建立的動態字體圖集只會在大小上改變。考慮到重建紋理圖集的開銷,應儘可能在重建過程當中減小圖集。有兩種方式能夠實現。
只要容許,儘量的使用非動態字體,預先設置所需支持的字符集。這適合用於使用有限字符集的UI,好比只使用Latin/ASCII字符,而且數量很小。
若是大量的文字須要被支持,好比整個Unicode字符集,那麼字體必須設置爲Dynamic。爲了不一些可預測的性能問題,能夠經過 Font.RequestCharactersInTexture 在啓動時將合適大小的字符集預先填充到字體的字符圖集中。
注意,任何一個Text組件改變都會觸發字體圖集的重建。當須要顯示大量文本組件時,最好是先收集Text組件內容的全部字符,再去填充圖集。這將確保字符圖集只被重建一次,而不是每加入一個新字符時就須要重建一次。
同時須要注意的是,當字體圖集觸發重建時,任何沒有被active Text組件包含的字符都不會放到新的圖集中,即便它以前經過 Font.RequestCharactersInTexture已經打入到圖集中。爲了解決這個限制,能夠監聽Font.textureRebuilt回調,查詢 Font.characterInfo 確保全部須要的字符包含在圖集中。
Font.textureRebuilt委託,目前尚未文檔。它是一個 single-argument Unity Event。它的參數是圖集被重建的字體。監聽這個事件應該按照如下格式:
public void TextureRebuiltCallback(Font rebuiltFont) { /* ... */ }
對於一些字符都是肯定的,而且字符之間的相對位置是固定的,那麼實現自定義的組件去顯示這些字符效率會更高。其中的一個例子就是分數顯示。
對於分數,可顯示的文字都在一個肯定的字符集中(數字0-9),而且不改變位置,和其餘字符保持固定的距離。將整數分解成數字,並用數字sprite顯示的開銷是微不足道的。這種專門的的數字顯示系統,可使用一種不須要分配,而且計算、動畫、顯示快速的方法去構建。它比Canvas驅動的UI Text組件更加高效。
對於必須支持大量字符集的應用程序,在字體導入器的「Font Names」字段中列出大量字體是很誘人的。當一個字符在主字體中不能被定位到,那麼「Font Names」字段列出的全部字體都有可能被用來當作後備字體。選擇後備字體的順序取決於字體在「Font Names」字段列表中的順序。
然而,爲了支持這種操做,Unity 須要將「Font Names」字段列出的全部字體都加載到內存中。若是字體集很是大,那麼後備字體將會佔用很是多的內存。這種問題常常在包含象形文字的字體(如日本文字和漢字)時出現。
一般,UI Text組件的 Best Fit 設置不該該被使用。
「Best Fit」能夠動態的調整字體的大小,讓字體在沒有超出邊界的狀況下,將字體調整到最大的整數值,還能夠設置最大/最小值來限制字體大小。然而因爲Unity的渲染器將全部顯示的不一樣大小的字符單獨的放入到字體圖集中,使用Best Fit設置,不一樣大小的字符將會很快的填滿圖集。
對於Unity5.3版本,使用Best Fit去檢測大小的算法並非最好的。它會將每個用於尺寸增量測試的字符都加入到字體圖集中,這進一步的增長了生成字體圖集所須要的時間。這也有可能致使字體移除,部分以前的字符會被移除圖集。因爲Best Fit 須要大量的測試計算,常常會產生別的Text組件使用的字符被移除,而且在合適的字體大小被計算出來以後,字體圖集又會被強制重建至少一次。
這個問題在Unity 5.4版本中被解決了,Best Fit 不會沒必要要的擴展字符圖集,可是它仍然比靜態大小的字體慢不少。
頻繁的字體重建會產生內存碎片,下降運行時的性能表現。設置Best Fit的文本組件越多,這個問題越嚴重。
在填充率問題以後, Unity UI的Scroll View 是第二常見的運行時性能問題的來源。Scroll View一般須要大量的UI元素來當作它的內容。有兩種基礎的填充Scroll View的方式:
兩種方法都有問題。
第一種方法會隨着須要實例化的UI元素增多,消耗更多的時間,而且會增長Scroll View重建的時間。若是ScrollView中只有少許的元素,例如在滾動視圖中,只須要顯示少許文本組件,那麼這種方式更簡潔。
第二種方法須要大量的方法去確保當前的UI和佈局系統能顯示正常。還有兩種可行方案將在下面講述。對於很是複雜的UI,一般使用緩存池的方法能夠在必定程度上減小性能開銷。
不管哪一種方法,在ScrollView上添加RectMask2D組件,均可以提高性能。當Canvas重建時,這個組件能夠確保Scroll View視窗外的元素不包括在必須具備生成,排序和分析其幾何的可繪製元素列表中。
最簡單的方式去實現scroll view的緩存池,同時保留Unity內置Scroll View組件的便利性就是使用一種混合的方法:
在UI中佈局元素,須要容許佈局系統正確的計算Scroll View內容的大小,而且讓滾動條運行正常。要佈局的時候,可使用掛載了Layout Element組件的GameObject爲可見的UI佔位。
而後,實例化一個可見元素的池,讓可見元素能夠填滿Scroll View的可見部分,而且將它們的父級設置爲佔位的GameObject。當Scroll View滾動時,重用UI元素來顯示已經滾動到視圖中的內容。
這實質上是減小必須被合批的UI元素的數量,由於合批的開銷只是依賴於Canvas中 Canvas Renderers的數量,而不是Rect Transform的數量。
當任何一個UI元素的父級變化或者它的sibling order發生變化,那麼這個元素和它的子元素都會被設置爲"dirty",而且強制它們的Canvas重建。
緣由是由於Unity還沒去區分父級變化和silbing order變化的回調。這兩個事件都會觸發OnTransformParentChanged的回調。在Unity UI的Graphic類(源碼中Graphic.cs)中,那個回調會調用SetAllDirty方法。由於被設置爲dirty,系統會保證這些Grapihc會在下一幀渲染以前重建它的佈局和頂點。
能夠爲每一個Scroll View中的元素的根RectTransform添加Canvas,這樣只會去重建父節點變化的元素,而不是整個Scroll View的內容。然而,這種方式會增長Scroll View渲染的drawcall。此外,若是Scroll View中的單個元素很複雜,包含了很是多的Graphic組件,而且還包含了不少佈局組件,那麼在低端機上重建它們的開銷會明顯的下降幀率。
若是Scroll View的UI元素的大小不會變化, 那麼沒有必要對所有的組件的佈局和頂點的從新計算。避免這種行爲,須要實現一個機遇位置變化的而不是父級或者sibling-order變化的對象池解決方案。
爲了不上述問題,能夠建立一個Scroll View經過移動它包含UI的RectTransform來實現緩存對象。這樣避免了重建那些已經移動過的而且大小沒有變化的RectTransform,能夠顯著的提高Scroll View的性能。
一般是經過寫一個自定義的Scroll View類,或者寫一個自定義的Layout Group組件來實現這個操做。後者一般是更簡單的解決方案,能夠經過實現Unity UI的LayoutGroup抽象基類的子類來實現。
自定義佈局組能夠分析潛在的源數據,以檢查必須顯示多少個數據元素,並能夠適當地調整Scroll View的內容RectTransform的大小。能夠監聽Scroll View change events事件,相應的調整可見元素的位置。