unity打包原理解析

工具

Unity 中的資源來源有三個途徑:一個是Unity自動打包資源,一個是Resources,一個是AssetBundle。

Unity自動打包資源是指在Unity場景中直接使用到的資源會隨着場景被自動打包到遊戲中,這些資源會在場景加載的時候由unity自動加載。這些資源只要放置在Unity工程目錄的Assets文件夾下即可,程序不需要關心他們的打包和加載,這也意味着這些資源都是靜態加載的。但在實際的遊戲開發中我們一般都是會動態創建GameObject,資源是動態加載的,因此這種資源其實不多。 Resources資源是指在Unity工程的Assets目錄下面可以建一個Resources文件夾,在這個文件夾下面放置的所有資源,不論是否被場景用到,都會被打包到遊戲中,並且可以通過Resources.Load方法動態加載。這是平時開發是常用的資源加載方式,但是缺點是資源都直接打包到遊戲包中了,沒法做增量更新。 AssetBundle資源是指我們可以通過編輯器腳本來將資源打包成多個獨立的AssetBundle。這些AssetBundle和遊戲包是分離的,可以通過WWW類來加載。AssetBundle的使用很靈活:可以用來做分包發佈,例如大多數頁遊資源是隨着遊戲的過程增量下載的,或者有些手遊資源過大,渠道要求發佈的包限制在100M以內,那隻能把一開始玩不到的內容做成增量包,等玩家玩到的時候通過網絡下載。AssetBundle 也可以用來做我們下面討論的自動增量更新。

Unity5相比之前的版本,AssetsBundle的打包過程有所簡化。之前打包需要通過代碼來設置需要打入包的資源並自己建立包的依賴關係,Unity5可以通過每個資源Inspector底部的AssetBundle下拉來指定該資源要打入哪個包,不指定就是不打包。打包過程只需要BuildPipeline.BuildAssetBundles一句話就行了,Unity5會根據依賴關係自動生成所有的包。每個包還會生成一個manifest文件,這個文件描述了包大小、crc驗證、包之間的依賴關係等等,通過這個manifest打包工具在下次打包的時候可以判斷哪些包中的資源有改變,只打包資源改變的包,加快了打包速度。manifest只是打包工具自己用的,發佈包的時候並不需要。

關於自動生成依賴關係這個有必要提下,Unity確實會自動給你建立依賴關係,前提是你依賴的資源必須已經在Inspector中設置了BundleName。如果沒有,Unity會把這個公用的資源重複打到多個用到的包中,因爲這個公用資源不在一個獨立的包中,Unity也不會智能地給你發現它是公用的,然後生成一個公用包。更深的坑在於,如果你公用的是一個FBX模型,你只給這個模型設置BundleName還不行,它用到的貼圖,材質都要設,否則模型是公用了,貼圖沒有公用,結果貼圖還是被打包到多個包中了。所以設置BundleName這個工作最好還是由編輯器腳本來完成。

方案

Unity提供的就這些了,下面就自己發揮:如何做一個方便的資源管理方案,既可以開發時方便,又可以方便發佈更新包呢?開發過程全用AssetsBundle是不合適的,因爲開發中資源經常添加和更新,每次添加或者更新都生成一下AssetsBundle才能運行是很麻煩的。而且我們要做的是自動更新而不是分包下載,這也就是說在發佈遊戲的時候這些資源應該都是在遊戲包中的,所以他們也不該從AssetsBundle加載。

分析完需求,方案也就出來了:資源還是放在Resources下面,但是這些資源同時也會打包到AssetBundle中。代碼中所有加載資源的地方都通過自己的ResourceManager來加載,由ResourceMananger來決定是調用Resources.Load來加載資源還是從AssetsBundle加載。在開發環境下(Editor)這些資源顯然是直接從Resources加載的,發佈的完整安裝包資源也是從Resources加載,只有當有一個增量版本時,遊戲主程序纔會去服務器把增量的AssetBundle下載下來,然後從AssetBundle加載資源。

實現

實現中我們首先要考慮的是AssetBundle的粒度,即每個AssetBundle包含多少資源。增量包的最小粒度就是AsssetBundle, 如果單個AssetBundle過大,只要這個AssetBundle中有一個資源改變了就需要重新下載整個AssetBundle,浪費流量和玩家的等待時間;如果單個AssetBundle過小,極端情況是每個資源一個AssetBundle,雖然實現了更新最小化,但是帶來了額外開銷:AssetBundle本身也是有大小的,而且查找加載AssetBundle也是需要時間的。大家都往U盤裏面拷過東西,拷一個1G的文件比拷1千個1M的文件要快很多。比較合理的做法是根據邏輯來,例如每個角色可以有獨立的AssetBundle,公用的一些UI資源可以打到一個AssetBundle裏面,每個場景獨立的UI資源可以打成獨立的AssetBundle。這樣做資源預加載的時候也方便,每個場景需要用到幾個Bundle就加載幾個Bundle,無關的資源不會被加載。

下面要考慮的是如何來確定一個資源是從Resources加載還是AssetBundle加載。爲此我們需要一個配置文件resourcesinfo。這個文件隨打包過程自動生成。裏面包含了資源版本號version,所有包的名字,每個包的HashCode以及每個包裏面包含的資源的名字。HashCode直接可以從Unity生成的manifest中得到(AssetBundleManifest.GetAssetBundleHash),用來檢查包的內容是否發生變化。這個resourceinfo每次打包AssetBundle時都會生成一個,發佈增量時將它和新的Bundle一起全部複製到服務器上。同時在Resources文件夾下也存一份,隨完整安裝包發佈,這就保證了新安裝遊戲的玩家手機上也有一份完整的資源配置文件,記錄了這個完整包包含的資源。

當遊戲啓動時,首先請求服務器檢查版本號,前端用的版本號就是Resources下面的這個resourcesinfo中的version。服務器比對這個版本號來告訴前端是否需要更新。如果需要更新,前端就去獲取服務器端的新resourcesinfo,然後比對裏面每個bundle的HashCode,把HashCode不同的bundle記錄下來,然後通過WWW類來下載這些發生改變的bundle,當然如果服務器版的resourcesinfo中包含了本地resourceinfo中所沒有的Bundle,這些Bundle就是新增的,也需要下載下來。所有下載完成後,前端將這個新的resourceinfo保存到本地存儲中,後面前端的所有操作都將以這個resourceinfo爲準而不再是Resources下面的resourceinfo了。Resources下的resourceinfo可以退出歷史舞臺了,除非一種情況:本地存儲的resourceinfo被認爲刪除了。手機端玩家清理應用的數據就會造成下載的bundle以及resourceinfo被刪除。沒關係,這時候前端由於找不到外部的resourceinfo了,還會使用Resources下面的resourceinfo和服務器比對,把新的bundle重新下載下來。

現在從哪裏加載資源就很明確了:ResourceMananger先讀取resourcesinfo,知道了遊戲中所有的Bundle和每個Bundle包含的資源,然後去外部存儲查找這些Bundle是否存在,如果存在,就記錄下這個Bundle的資源應該從外部的AssetBundle加載,如果不存在,就從內部的Resources加載。在開發過程(Editor)中,由於不存在外部存儲的bundle,資源自然都是從Resources加載的,達到了我們開發方便的目的這個過程隱含了一點:不是所有的資源都需要有BundleName而被打包到AssetBundle中,遊戲內不需要後續更新的資源就不要設置BundleName,它們不會被打包更新,這樣的資源ResourceManager在resourceinfo中是找不到的,直接去Resources文件夾下面讀取就行了。

加載AssetBundle,我們直接使用WWW類而不用WWW.LoadFromCacheOrDownload, 因爲我們的資源在遊戲開始的時候已經下載到外部存儲了,不要再Download也不要再Cache。注意WWW類加載是異步的,在遊戲中我們需要同步加載資源的地方就要注意把資源預加載好存在ResourceManager中,不然等用的時候加載肯定要寫異步代碼了。大部分時候我們應該在一個場景初始化時就預加載好所有資源,用的時候直接從ResourceManager的緩存取就可以了。

資源加載卸載

最後簡單說下資源的加載卸載,這個網上也有很多文章介紹。 
從我理解來看Resources是一個缺省自動打包的特殊AssetBundle。無論
從WWW還是AssetBundle.CreateFromFile創建AssetBundle其實是創建了一個文件內存鏡像。這時候是沒有Asset的。AssetBundle.LoadAsset 和Resource.Load才真正創建出了Asset,而Instaniate複製了這個Asset。注意這個複製有兩種,學C++的都知道淺拷貝和深拷貝,這裏的複製有的是正真的複製,有的是引用。爲什麼要這樣呢?因爲有些遊戲資源是隻讀的,像貼圖Texture,這麼大而且只讀,當然不需要再去完全複製一份。但像GameObject這種資源它的屬性是可以通過腳本改變的,必須要複製一份。所以一個資源從AssetBundle到場景中被實例化,其實有3塊內存被創建,這3快內存的釋放是有不同方法的

文件內存鏡像是通過AssetBundle.Unload(false)來釋放的。 Instaniate出來的Object內存通過Object.Destory來釋放 AssetBundle.Unload(true)不單會釋放文件內存鏡像,還會釋放AssetBundle.Load創建的Assets。這個方法是不安全的,除非你能保證這些Assets沒有Object在引用,否則就出問題了。 Resources.UnloadAsset和Resources.UnloadUnusedAssets可以用來釋放Asset。

下面這個圖很直觀:


這是老Unity的圖,Unity5已經把AssetBundle.Load 改成了AssetBundle.LoadAsset。這個改動讓我們更明確了Load出來的是Asset這塊內存區域。什麼時候把Resource.Load也改了吧。

注意事項(坑)

Resources.Load方法傳入的資源路徑需是從Resources文件夾下一級開始的相對路徑且不能包含擴展名;而AssetBundle.LoadAsset方法傳入的資源名需是從Assets文件開始的全路徑且要包含擴展名。路徑不區分大小寫,建議全用小寫,因爲AssetBundle.GetAllAssetNames方法返回的資源名都是小寫的。 Unity5打包AssetBundle時會自動處理依賴關係,但是在運行時加載的時候卻不會,程序需要自己處理,先加載依賴包。 AssetBundle.CreateFromFile不能加載壓縮過的AssetBundle,所以我們只能用WWW來異步加載AssetBundle。 目前我用的Unity5.0.2f1的Resources.Load方法在手機端比原來慢了很多,如果以前可以不緩存每次用的時候都調用Resource.Load現在就不行了。頻繁的調用會導致明顯的性能開銷,不知道是不是Bug。

原文地址:http://blog.csdn.net/ring0hx/article/details/46376709




包依賴關係
加載關係
1.獲取依賴關係?
都是從manifeset 文件中獲取
程序實現 www manifeset = new www(manifesetpath);
Manifeset.assetboundle.loadasset("AssetBoundleManifest");
//AssetBoundleManifest參數定死
2.配置文件  包名保存了記錄關係
3.根據依賴關係加載資源

4.加載我們的assetboundle
5. 加載資源
Assetboundle.loadAsset(resName);
Assetboundle.LoadAssetWithSubAssets(resName);// 加載多個資源  圖片包含多個子選項類型

卸載關係
卸載對象發現消除依賴對象關聯時,發現對象無依賴則直接卸載依賴項.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章