1. 程式人生 > >Unity5 怎樣做資源管理和增量更新

Unity5 怎樣做資源管理和增量更新

能開 網上 mes var file 刪除 bundle text 就會

工具

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

  • Unity自己主動打包資源是指在Unity場景中直接使用到的資源會隨著場景被自己主動打包到遊戲中。這些資源會在場景載入的時候由unity自己主動載入。這些資源僅僅要放置在Unityproject目錄的Assets目錄下即可,程序不須要關心他們的打包和載入,這也意味著這些資源都是靜態載入的。

    但在實際的遊戲開發中我們一般都是會動態創建GameObject。資源是動態載入的,因此這樣的資源事實上不多。

  • Resources資源是指在Unityproject的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載入,僅僅有當有一個增量版本號時,遊戲主程序才會去server把增量的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一起全部拷貝到server上。

同一時候在Resources目錄下也存一份,隨完整安裝包公布,這就保證了新安裝遊戲的玩家手機上也有一份完整的資源配置文件,記錄了這個完整包包括的資源。

當遊戲啟動時。首先請求server檢查版本號號。前端用的版本號號就是Resources以下的這個resourcesinfo中的version。

server比對這個版本號號來告訴前端是否須要更新。假設須要更新。前端就去獲取server端的新resourcesinfo。然後比對裏面每一個bundle的HashCode,把HashCode不同的bundle記錄下來。然後通過WWW類來下載這些發生改變的bundle,當然假設server版的resourcesinfo中包括了本地resourceinfo中所沒有的Bundle,這些Bundle就是新增的。也須要下載下來。全部下載完畢後。前端將這個新的resourceinfo保存到本地存儲中。後面前端的全部操作都將以這個resourceinfo為準而不再是Resources以下的resourceinfo了。Resources下的resourceinfo能夠退出歷史舞臺了。除非一種情況:本地存儲的resourceinfo被覺得刪除了。

手機端玩家清理應用的數據就會造成下載的bundle以及resourceinfo被刪除。沒關系,這時候前端由於找不到外部的resourceinfo了,還會使用Resources以下的resourceinfo和server比對。把新的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

Unity5 怎樣做資源管理和增量更新