2016年7月18日 星期一

Unity專案資源載入與管理

作者:柳振東 Unity官方平台

之前分享了Unity專案設計與管理的一些注意事項,其中最重要的莫過於資源載入與管理。今天這篇文章將由Unity官方技術支持工程師柳振東,針對一些常見的Unity專案資源載入與管理問題進行解答。


 Q.將材質打包為AssetBundle,執行時使用LoadAsset載入材質也成功了,為什麼還會出現材質丟失的情況呢?


這也是開發者經常會遇到的問題。一般情況都是開發者先把被依賴AssetBundle的資源載入出來,然後用Unload卸載掉這個被依賴的AssetBundle,最後發現從其它AssetBundle中載入出來的GameObject丟失了前面載入出來的資源。

要弄明白這個問題,我們需要瞭解AssetBundle中的Asset在載入時尋找其依賴資源的規則。其實很簡單,現在AssetBundle實現的機制是只會在記憶體中尋找其依賴資源所在的AssetBundle,並自動從中載入出所需的資源。

那麼現在我們知道,其實通過LoadAsset從被依賴資源的AssetBundle中載入出來的資源,除非我們手動綁定回主體資源中(例如把載入出來的材質通過代碼綁定回GameObject的Renderer中),否則是不會被自動索引到的。

也就是說,通過AssetBundle來動態載入資源時,我們並不需要自己載入被依賴的資源,而是只要保證主體在載入時被依賴資源所在AssetBundle依然處於開啟狀態就可以正常載入資源了。


Q.為什麼遊戲切換到後臺一段時間後再切換回來材質會變成粉色呢?

大家都應該知道,粉色材質在Unity中是Shader出錯或丟失時的預設警告材質,而在這種情況下就是Shader丟失的問題。

首先我們要瞭解一個系統對於GPU資源管理的機制。在PC平台上,玩家在鎖屏一段時間後GPU會自動把部分幕後程式的資源清除掉,而Android或iOS這種手機系統一般會在切換到主介面或其它程式一段時間後進行類似的操作。

那麼當Unity程式在GPU中的Shader和Texture等資源被清除之後再切換回該程式時,CPU端會接收到GPU Graphics Context 丟失的消息,然後Unity會嘗試從記憶體中將丟失的資源重新載入到GPU端。

這時,如果原來的Shader和Texture資源是從AssetBundle中載入出來的,並且該AssetBundle已經被卸載掉的話,那麼Unity就無法再從記憶體中載入到這些資源,從而導致GPU丟失Shader與Texture了。

可能有朋友會說,這些Shader和Texture不是曾經被載入到記憶體中嗎?是的,但是它們在被載入到GPU之後會被從記憶體中清除掉。因此要防止這種問題的發生最穩健的辦法就是Shader和Texture的AssetBundle在場景切換前都不要卸載掉。
而如果擔心AssetBundle本身消耗記憶體問題的話可以參考下一個問題的解答。


Q.新的ChunkBasedCompression壓縮方式相比原來的壓縮方式有什麼區別呢,應該如何取捨呢?


Unity預設的AssetBundle壓縮方式是LZMA,這種壓縮方式的AssetBundle在載入到記憶體時會馬上執行解壓過程,並在AssetBundle處於開啟狀態期間在記憶體中保留一個解壓過後的Cache(例外情況:UnityWebRequest.GetAssetBundle與WWW.LoadFromCacheOrDownload均會在第一次解壓LZMA AssetBundle時把解壓後版本Cache到檔案系統中,後續的調用就無需在記憶體中保留解壓後的AssetBundle了),因此記憶體佔用會比較明顯。

而Unity5.3以後提供的ChunkBasedCompression是一種基於Chunk的LZ4壓縮方式,這種壓縮方式可以讓AssetBundle對單獨的Asset進行壓縮,而不是AssetBundle整體壓縮。因此載入這種壓縮方式的AssetBundle時,無需事先解壓,只需在記憶體中保留一個頭結構,然後在載入某個Asset的時候才即時從檔中讀取該Asset所在的chunk並解壓到記憶體中。

由於LZ4是公認的解壓速度極快的壓縮方式,並且需要即時解壓的資料量一般不會很大,因此即時解壓Asset帶來的CPU時間消耗其實很小,另一方面把解壓時間分散在不同地方也減輕了一次性解壓帶來的卡頓問題。當然,最重要的優點還是在於大大減輕了記憶體的壓力,可以放心地讓有可能被再次索引的AssetBundle常駐在記憶體中,因此我們非常推薦大家使用LZ4壓縮方式的AssetBundle。

不過大家在使用過LZ4壓縮方式後應該也會發現,在資源數比較多的情況下,LZ4格式的AssetBundle大小基本都要比LZMA格式的大一些,這也是分塊壓縮不可避免的缺陷。但考慮到它的記憶體消耗表現優秀,移動端的開發朋友應該可以忽略這一點吧。


Q.Resources資料夾裡的資源越多,程式的啟動畫面時間就越長。這是為什麼呢?


要理解這個問題,我們需要知道Unity程式啟動時的一個操作。在啟動載入的過程中,Unity需要為Resources資料夾(此時其實就是一個序列化檔)中的所有資源構建一個查找樹作為後面載入具體Asset時所需的索引資料,而這個結構的構造時間是非線性的(比線性稍高一點),因此在Resources資料夾中的檔越多,啟動載入的時間就越明顯,基本10000個資源在一些低端手機上需要5到10秒的載入時間。

另一方面,考慮到Resources資料夾無法動態更新,也沒有AssetBundle Variant這種設定不同資源版本的功能,因此我們極力推薦大家主要採用AssetBundle進行資源的動態載入,而Resources資料夾的使用可以只考慮這幾種情況:
  1. 這些資源在整個遊戲的運行期間都會用到
  2. 這些資源無需為不同平臺或硬體配合來自訂資源
  3. 這些資源無需動態更新
  4. 架構遊戲的雛形

Q.程式中主要使用AssetBundle.Unload(false)卸載AssetBundle,有時會發現AssetBundle中的資源在記憶體中會存在多份,這是為什麼呢?


這種問題產生的根源在於從AssetBundle中載入出來的資源,在該AssetBundle卸載之後與此AssetBundle的聯繫就斷開了。舉個例子,我從AssetBundle A 中載入出來一個Prefab p1, 那麼p1本身依賴的資源,例如一個Texture tex1也會自動載入到記憶體中。然後我用AssetBundle.Unload(false)來卸載AssetBundle A,此時p1與AssetBundle A已斷開關係。之後過了一段時間,我需要從AssetBundle A中載入另一個Prefab p2 ,假設p2也依賴於Texture tex1,那麼我再次載入AssetBundle A,從中載入p2時tex1會再次被載入到記憶體中,導致此時記憶體中存在兩份tex1。


這個AssetBundle的資源索引策略我們官方在之後的版本中會進行修改以避免這種情況產生的記憶體消耗。而現階段大家其實只要注意AssetBundle的卸載時機即可避免此情況的發生,意思就是在保證當下場景中同一AssetBundle不會再被引用的時候卸載或者統一都在場景切換的時候使用Unload(true)進行卸載。

關於AssetBundle的常見問題解答就分享到這裡,如果還有其它疑問,也可在訪問Unity官方中文社區(forum.china.unity3d.com)發帖提問。

沒有留言:

張貼留言

著作人