首先附上原文鏈接:https://unity3d.com/learn/tutorials/topics/best-practices/assetbundle-usage-patterns
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?AssetBundle使用模式
? ? ? ? 本文是Unity5中關(guān)于Asset奕塑、Resources和資源管理系列文章的第五章玫霎。
? ? ? ? 本系列的前一章介紹了AssetBundles的基礎(chǔ)知識(shí)抒线,其中包括各種加載API的低級(jí)行為述召。本章討論在實(shí)際中使用AssetBundle的各個(gè)方面的問(wèn)題和可能的解決方案。
管理已經(jīng)加載的Asset
? ? ? ? 在內(nèi)存敏感的環(huán)境中仔細(xì)控制加載的Object的大小和數(shù)量非常重要笆搓。當(dāng)對(duì)象從活動(dòng)的場(chǎng)景中移除時(shí)膏蚓,Unity不會(huì)自動(dòng)卸載它們。Asset清理會(huì)在特定時(shí)間觸發(fā)兼呵,也可以通過(guò)手動(dòng)方式觸發(fā)兔辅。
? ? ? ? AssetBundles本身必須被小心管理。本地文件存儲(chǔ)支持的一個(gè)AssetBundle(包括在Unity中緩存的或是通過(guò)AssetBundle.LoadFromFile加載的)有最小的內(nèi)存開(kāi)銷(xiāo)击喂,很少消耗超過(guò)幾十KB维苔。但是,如果存在大量的AssetBundles懂昂,則此開(kāi)銷(xiāo)仍可能會(huì)出現(xiàn)問(wèn)題介时。
? ? ? ? 由于大多數(shù)項(xiàng)目允許用戶去重新體驗(yàn)內(nèi)容(比如重新進(jìn)入關(guān)卡),因此知道何時(shí)加載或卸載AssetBundle非常重要凌彬。如果AssetBundle卸載不當(dāng)沸柔,可能會(huì)導(dǎo)致內(nèi)存中的對(duì)象重復(fù)。 在某些情況下不當(dāng)卸載AssetBundles也會(huì)導(dǎo)致不可預(yù)期的行為铲敛,例如導(dǎo)致紋理丟失褐澎。要理解為什么會(huì)發(fā)生這種情況,請(qǐng)參閱Asset伐蒋,Object和序列化章節(jié)的“ Object間引用”部分工三。
? ? ? ? 管理Asset和AssetBundles時(shí)要理解的最重要的事情是調(diào)用AssetBundle.Unload時(shí)unadAllLoadedObjects參數(shù)是true或false的行為差異。
? ? ? ? 該API將卸載正在調(diào)用的AssetBundle的數(shù)據(jù)頭先鱼。這個(gè)unloadAllLoadedObjects的參數(shù)決定是否還卸載從這個(gè)AssetBundle實(shí)例化的所有Object徒蟆。如果設(shè)置為true,則源于該AssetBundle的所有Object也將立即被卸載 - 即使它們當(dāng)前正在活動(dòng)場(chǎng)景中使用型型。
? ? ? ? 例如段审,假設(shè)matreial M是從AssetBundle AB中加載的,并且假設(shè)M當(dāng)前處于活動(dòng)場(chǎng)景中。(圖片見(jiàn)原網(wǎng)頁(yè))
? ? ? ? 如果調(diào)用了AssetBundle.Unload(true)寺枉,那么M將從場(chǎng)景中移除抑淫,銷(xiāo)毀并卸載。但是姥闪,如果調(diào)用AssetBundle.Unload(false)始苇,則AB的數(shù)據(jù)頭將被卸載,但M仍將保留在場(chǎng)景中并且仍然有效筐喳。調(diào)用AssetBundle.Unload(false)會(huì)中斷M和AB之間的鏈接催式。如果稍后再次加載AB,則AB中包含的該Object的新副本將被加載到內(nèi)存中避归。
? ? ? ? 如果AB再次被加載荣月,則將重新加載AssetBundle數(shù)據(jù)頭的新副本。但是梳毙,M并未從AB的這個(gè)新副本中加載哺窄。Unity不會(huì)在AB的新副本和M之間建立任何關(guān)聯(lián)。
? ? ? ? 如果使用AssetBundle.LoadAsset()來(lái)重新加載M账锹,Unity不會(huì)認(rèn)為舊的M副本是AB數(shù)據(jù)的一個(gè)實(shí)例萌业。因此,Unity將加載M的新副本奸柬,并且在場(chǎng)景中將有兩個(gè)相同的M副本生年。
? ? ? ? 對(duì)于大多數(shù)項(xiàng)目來(lái)說(shuō),這種行為是不希望出現(xiàn)的廓奕。大多數(shù)項(xiàng)目應(yīng)該使用AssetBundle.Unload(true)并采用一種方法來(lái)確保對(duì)象不重復(fù)抱婉。兩種常用方法是:
? ? ? ? 1.在應(yīng)用程序生命周期中具有明確定義的點(diǎn)(如關(guān)卡切換和屏幕加載)卸載臨時(shí)的AssetBundle。這是更簡(jiǎn)單和最常見(jiàn)的選擇懂从。
? ? ? ? 2.維持各個(gè)Object的引用數(shù)量授段,僅當(dāng)它們所有構(gòu)成的Object都處于未使用時(shí)蹲蒲,才卸載AssetBundles番甩。這允許應(yīng)用程序卸載并重新加載各個(gè)Object而不存在同樣的內(nèi)存。
? ? ? ? 如果應(yīng)用程序必須使用AssetBundle.Unload(false)届搁,那么各個(gè)Object只能通過(guò)兩種方式卸載:
? ? ? ? 1.在場(chǎng)景和代碼中消除對(duì)所有不需要對(duì)象的所有引用缘薛。在這完成后,調(diào)用Resources.UnloadUnusedAssets卡睦。
? ? ? ? 2.非附加性的加載場(chǎng)景宴胧,這將銷(xiāo)毀在當(dāng)前場(chǎng)景中的所有Object,并自動(dòng)調(diào)用Resources.UnloadUnusedAssets表锻。
? ? ? ? 如果一個(gè)項(xiàng)目有明確定義的點(diǎn)恕齐,可以使用戶等待Object加載和卸載(例如在游戲模式或關(guān)卡之間),則應(yīng)使用這些點(diǎn)來(lái)卸載盡可能多的Obejct并加載新的Object瞬逊。
? ? ? ? 最簡(jiǎn)單的方法是將項(xiàng)目的獨(dú)立塊打包到場(chǎng)景中显歧,然后將這些場(chǎng)景及其所有依賴項(xiàng)構(gòu)建到AssetBundles中仪或。應(yīng)用程序可以進(jìn)入“加載”場(chǎng)景,完全卸載包含舊場(chǎng)景的AssetBundle士骤,然后加載包含新場(chǎng)景的AssetBundle范删。
? ? ? ? 雖然這是最簡(jiǎn)單的流程,但有些項(xiàng)目需要更復(fù)雜的AssetBundle管理拷肌。由于每個(gè)項(xiàng)目都不同到旦,因此沒(méi)有通用的AssetBundle設(shè)計(jì)模式。
? ? ? ? 在決定如何將對(duì)象分組進(jìn)AssetBundles時(shí)巨缘,如果一些Object必須同時(shí)加載或者卸載添忘,那么通常最好先將它們打成一個(gè)AssetBundle。例如带猴,一個(gè)RPG游戲昔汉,個(gè)別地圖和過(guò)場(chǎng)動(dòng)畫(huà)可按場(chǎng)景分組為AssetBundle,但一些Object在大多數(shù)場(chǎng)景中都需要拴清“胁。可以構(gòu)建一個(gè)AssetBundles來(lái)提供肖像,游戲中的用戶界面以及不同的角色模型和紋理口予。這些Object和Asset可以被分組到第二個(gè)AssetBundle娄周,它們?cè)陂_(kāi)始時(shí)被加載并在整個(gè)應(yīng)用程序生命周期之內(nèi)保持加載狀態(tài)。
? ? ? ? 如果Unity必須在AssetBundle卸載后從它的AssetBundle重新加載對(duì)象沪停,則可能會(huì)出現(xiàn)另一個(gè)問(wèn)題煤辨。在這種情況下,重新加載將失敗木张,Object將作為(Missing)Object出現(xiàn)在Unity編輯器的層級(jí)結(jié)構(gòu)中众辨。
? ? ? ? 這主要發(fā)生在Unity丟失并重新獲取對(duì)其圖形上下文的控制時(shí),例如當(dāng)移動(dòng)應(yīng)用程序被暫拖侠瘢或用戶鎖定其PC時(shí)鹃彻。在這種情況下,Unity必須將texture和shader重新上傳到GPU妻献。如果這些Asset的源AssetBundle不可用蛛株,則應(yīng)用程序?qū)⒁匝蠹t色呈現(xiàn)場(chǎng)景中的Object。
4.2 分發(fā)
? ? ? ? 將項(xiàng)目的AssetBundles分發(fā)給客戶端有兩種基本方式:與項(xiàng)目同時(shí)安裝或在安裝后下載育拨。
? ? ? ? AssetBundle是否跟隨安裝包安裝或是在安裝完成后下載取決于項(xiàng)目將要運(yùn)行平臺(tái)的能力和限制谨履。移動(dòng)項(xiàng)目通常選擇安裝后下載,以減少初始安裝大小并保持在無(wú)線下載大小限制以下熬丧∷袼冢控制臺(tái)和PC項(xiàng)目通常會(huì)在應(yīng)用程序初始安裝時(shí)安裝AssetBundle。
? ? ? ? 正確的體系結(jié)構(gòu)允許在安裝后將新內(nèi)容或修訂的內(nèi)容添加到補(bǔ)丁中,而不管最初如何交付AssetBundles害捕。有關(guān)這方面的更多信息唆香,請(qǐng)參閱Unity手冊(cè)的“ 使用AssetBundles進(jìn)行修補(bǔ)”部分。
4.2.1 隨項(xiàng)目一起安裝
? ? ? ? 將AssetBundles與項(xiàng)目一起安裝是最簡(jiǎn)單的發(fā)布方式吨艇,因?yàn)樗恍枰~外的下載管理代碼躬它。為什么一個(gè)項(xiàng)目可能會(huì)在安裝時(shí)包含AssetBundles有兩個(gè)主要原因:
? ? ? ? ·減少項(xiàng)目構(gòu)建時(shí)間并允許更簡(jiǎn)單的迭代開(kāi)發(fā)。如果這些AssetBundles不需要與應(yīng)用程序分開(kāi)更新东涡,那么AssetBundle可以通過(guò)將AssetBundle存儲(chǔ)在Streaming Assets文件夾中而包含在應(yīng)用程序中冯吓。請(qǐng)參閱下面的Streaming Assets部分。
? ? ? ? ·發(fā)布可更新內(nèi)容的初始版本疮跑。通常這樣做是為了節(jié)省最終用戶在初次安裝后的時(shí)間组贺,或者作為后續(xù)打補(bǔ)丁的基礎(chǔ)。Streaming Assets對(duì)于這種情況并不適合祖娘。但是失尖,如果編寫(xiě)自定義下載和緩存系統(tǒng)不是一種選擇,則可以從Streaming Assets文件夾中將可更新內(nèi)容的初始版本加載到Unity緩存中渐苏。
4.2.1.1 Streaming Assets
? ? ? ? 在一個(gè)Unity應(yīng)用程序安裝時(shí)包含所有類(lèi)型的內(nèi)容(包括AssetBundle)的最簡(jiǎn)單的方式是在構(gòu)建項(xiàng)目之前將內(nèi)容放入 /Assets/StreamingAssets/文件夾掀潮。構(gòu)建時(shí)包含在StreamingAssets文件夾中的任何內(nèi)容都將被復(fù)制到最終的應(yīng)用程序中。
? ? ? ? 本地存儲(chǔ)上StreamingAssets文件夾的完整路徑可在運(yùn)行時(shí)通過(guò)Application.streamingAssetsPath屬性訪問(wèn)琼富。然后可以在大多數(shù)平臺(tái)上通過(guò)AssetBundle.LoadFromFile加載AssetBundles仪吧。
? ? ? ? Android開(kāi)發(fā)人員:在Android上,StreamingAssets文件夾中的資源存儲(chǔ)在APK中鞠眉,并且如果它們被壓縮可能會(huì)需要更多時(shí)間才能加載薯鼠,因?yàn)榇鎯?chǔ)在APK中的文件可能使用不同的存儲(chǔ)算法,使用的算法可能會(huì)因Unity版本而異械蹋。您可以使用7-zip等存檔器打開(kāi)APK以確定文件是否被壓縮出皇。如果它們確實(shí)被壓縮了,您可以預(yù)料到AssetBundle.LoadFromFile()執(zhí)行得會(huì)更慢哗戈。如果是這樣郊艘,您可以使用UnityWebRequest.GetAssetBundle檢索緩存版本作為解決方法。通過(guò)使用UnityWebRequest谱醇,AssetBundle將在第一次運(yùn)行期間解壓縮并緩存暇仲,從而使后續(xù)執(zhí)行速度更快步做。請(qǐng)注意副渴,這將需要更多的存儲(chǔ)空間,因?yàn)锳ssetBundle將被復(fù)制到緩存中全度≈缶纾或者,您可以導(dǎo)出您的Gradle項(xiàng)目,并在構(gòu)建時(shí)向您的AssetBundles添加擴(kuò)展勉盅。然后佑颇,您可以編輯build.gradle文件并將該擴(kuò)展添加到無(wú)壓縮部分。完成后草娜,您應(yīng)該可以使用AssetBundle.LoadFromFile()而無(wú)需支付解壓縮性能成本挑胸。
? ? ? ? 注意:StreamingAssets在某些平臺(tái)上不是可寫(xiě)位置。如果項(xiàng)目在安裝后需要更新AssetBundle宰闰,則可以使用WWW.LoadFromCacheOrDownload或編寫(xiě)自定義下載程序茬贵。
4.2.2 安裝后下載
? ? ? ? 將AssetBundles交付給移動(dòng)設(shè)備的最佳方法是在應(yīng)用程序安裝后下載它們。這也允許在安裝后更新內(nèi)容而不強(qiáng)制用戶重新下載整個(gè)應(yīng)用程序移袍。在許多平臺(tái)上解藻,應(yīng)用程序二進(jìn)制文件必須經(jīng)過(guò)昂貴且冗長(zhǎng)的重新認(rèn)證過(guò)程。因此葡盗,開(kāi)發(fā)一個(gè)良好的安裝后下載系統(tǒng)至關(guān)重要螟左。
? ? ? ? 交付AssetBundles的最簡(jiǎn)單方法是將它們放置在Web服務(wù)器上并通過(guò)UnityWebRequest交付。Unity會(huì)自動(dòng)將下載的AssetBundles緩存在本地存儲(chǔ)上觅够。如果下載的AssetBundle是LZMA壓縮的胶背,則AssetBundle將以未壓縮或重新壓縮為L(zhǎng)Z4(取決于Caching.compressionEnabled設(shè)置)存儲(chǔ)在緩存中,以便將來(lái)加載更快喘先。如果下載的AssetBundle是LZ4壓縮的奄妨,那么AssetBundle將被壓縮存儲(chǔ)。如果緩存填滿苹祟,Unity將從緩存中刪除最近最少使用的AssetBundle砸抛。有關(guān)更多詳細(xì)信息,請(qǐng)參閱內(nèi)置緩存部分树枫。
? ? ? ? 如果可能通常建議使用UnityWebRequest來(lái)啟動(dòng)直焙,如果使用Unity 5.2或更老版本只使用WWW.LoadFromCacheOrDownload,如果內(nèi)置API的內(nèi)存消耗砂轻,緩存行為或性能對(duì)于特定項(xiàng)目不可接受奔誓,或者項(xiàng)目必須運(yùn)行特定于平臺(tái)的代碼以實(shí)現(xiàn)其要求,則只寄希望于自定義下載系統(tǒng)搔涝。
? ? ? ? 可能影響使用UnityWebRequest或WWW.LoadFromCacheOrDownload的情況示例:
? ? ? ? ·當(dāng)需要對(duì)AssetBundle緩存進(jìn)行細(xì)粒度控制時(shí)
? ? ? ? ·當(dāng)項(xiàng)目需要實(shí)施自定義壓縮策略時(shí)
? ? ? ? ·當(dāng)項(xiàng)目希望使用平臺(tái)特定的API來(lái)滿足某些要求時(shí)厨喂,例如需要在非活動(dòng)狀態(tài)下傳輸數(shù)據(jù)。
? ? ? ? ? ? ? ? ? ?——比如:使用iOS的后臺(tái)任務(wù)API在后臺(tái)下載數(shù)據(jù)庄呈。
? ? ? ? ·當(dāng)AssetBundles必須在Unity沒(méi)有適當(dāng)?shù)腟SL支持的平臺(tái)(如PC)上通過(guò)SSL提供蜕煌。
4.2.3 內(nèi)置緩存
? ? ? ? Unity有一個(gè)內(nèi)置的AssetBundle緩存系統(tǒng),可用于緩存通過(guò)UnityWebRequest API下載的AssetBundle诬留,該API包含一個(gè)接受AssetBundle版本號(hào)作為參數(shù)的重載斜纪。此編號(hào)不存儲(chǔ)在AssetBundle內(nèi)部踢俄,并且不由 AssetBundle系統(tǒng)生成携冤。
? ? ? ? 緩存系統(tǒng)對(duì)傳遞給UnityWebRequest的最新版本號(hào)保持追蹤惯吕。當(dāng)此API伴隨著一個(gè)版本號(hào)調(diào)用時(shí)倒庵,緩存系統(tǒng)通過(guò)比對(duì)版本號(hào)來(lái)檢查是否已經(jīng)緩存了這個(gè)AssetBundle。如果此編號(hào)匹配因块,系統(tǒng)將會(huì)加載緩存的AssetBundle橘原。如果此編號(hào)不匹配,或是沒(méi)有一個(gè)緩存好的AssetBundle涡上,那么Unity將下載一個(gè)新副本靠柑。這個(gè)新副本將與新版本號(hào)相關(guān)聯(lián)。
? ? ? ? 緩存系統(tǒng)中的AssetBundles僅由其文件名來(lái)辨認(rèn)吓懈,而不是由其被下載的完整URL標(biāo)識(shí)歼冰。這意味著具有相同文件名的AssetBundle可以存儲(chǔ)在多個(gè)不同的位置,比如一個(gè)CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))耻警。只要文件名稱相同隔嫡,緩存系統(tǒng)就會(huì)將它們識(shí)別為具有相同的AssetBundle。
? ? ? ? 每個(gè)應(yīng)用程序都要確定一個(gè)將版本號(hào)分配給AssetBundles的適當(dāng)策略甘穿,并將這些數(shù)字傳遞給UnityWebRequest腮恩。這些編號(hào)可以是各種獨(dú)立的標(biāo)識(shí)符種類(lèi),例如CRC值温兼。請(qǐng)注意秸滴,盡管AssetBundleManifest.GetAssetBundleHash()也可用于此目的,但我們不建議使用此功能進(jìn)行版本控制募判,因?yàn)樗鼉H提供估算值荡含,而不是真正的哈希值計(jì)算。
? ? ? ? 有關(guān)更多詳細(xì)信息届垫,請(qǐng)參閱Unity手冊(cè)的“ 使用AssetBundles進(jìn)行補(bǔ)丁”部分释液。
? ? ? ? 在Unity 2017.1以后,緩存API已經(jīng)擴(kuò)展提供更多的粒度控制装处,允許開(kāi)發(fā)人員從多個(gè)緩存中選擇一個(gè)活動(dòng)緩存误债。以前的Unity版本只能修改Caching.expirationDelay和Caching.maximumAvailableDiskSpace以刪除緩存的項(xiàng)目(這些屬性保留在Cache類(lèi)中的Unity 2017.1中)。
? ? ? ? expirationDelay是一個(gè)AssetBundle自動(dòng)刪除前必須經(jīng)過(guò)的最小秒數(shù)妄迁。如果在這期間AssetBundle沒(méi)有被訪問(wèn)寝蹈,那么它將會(huì)被自動(dòng)刪除。
? ? ? ? maximumAvailableDiskSpace指定本地空間存儲(chǔ)量(以字節(jié)為單位)登淘,它是緩存在達(dá)到expirationDelay時(shí)間開(kāi)始刪除AssetBundle前可以使用的存儲(chǔ)空間箫老。達(dá)到限制時(shí),Unity將刪除最近最少打開(kāi)的緩存中的AssetBundle(或通過(guò)Caching.MarkAsUsed標(biāo)記為已使用)形帮。Unity會(huì)刪除緩存的AssetBundles槽惫,直到有足夠的空間完成新的下載為止。
4.2.3.1 緩存填充
? ? ? ? 由于AssetBundles由其文件名標(biāo)識(shí)辩撑,因此可以使用應(yīng)用程序附帶的AssetBundles“填充”緩存界斜。為此,請(qǐng)將每個(gè)AssetBundle的初始版本或基本版本存儲(chǔ)在/ Assets / StreamingAssets /中合冀。該過(guò)程與“隨項(xiàng)目安裝”部分中詳細(xì)介紹的過(guò)程相同各薇。
? ? ? ? 第一次運(yùn)行應(yīng)用程序時(shí),可以通過(guò)從Application.streamingAssetsPath加載AssetBundles來(lái)填充緩存君躺。從此以后峭判,應(yīng)用程序可以正常調(diào)用UnityWebRequest(UnityWebRequest也可以用于最初從StreamingAssets路徑加載AssetBundles)。
4.2.3 自定義下載程序
? ? ? ? 編寫(xiě)自定義下載程序可讓?xiě)?yīng)用程序完全控制AssetBundles的下載棕叫,解壓縮和存儲(chǔ)方式林螃。由于所涉及的工程工作意義重大,我們只為大型團(tuán)隊(duì)推薦此方法俺泣。編寫(xiě)自定義下載器時(shí)有四個(gè)主要考慮事項(xiàng):
? ? ? ? ·下載機(jī)制
? ? ? ? ·存儲(chǔ)位置
? ? ? ? ·壓縮類(lèi)型
? ? ? ? ·補(bǔ)丁
? ? ? ? 有關(guān)AssetBundle補(bǔ)丁的信息疗认,請(qǐng)參閱Unity手冊(cè)中使用AssetBundle補(bǔ)丁部分。
4.2.3.1 下載
? ? ? ? 對(duì)于大多數(shù)的應(yīng)用程序來(lái)說(shuō)伏钠,HTTP是下載AssetBundles最簡(jiǎn)單的方法横漏。但是,實(shí)現(xiàn)一個(gè)基于HTTP的下載程序不是最簡(jiǎn)單的任務(wù)熟掂。自定義下載程序必須避免過(guò)多的內(nèi)存分配缎浇,過(guò)多的線程使用和過(guò)多的線程喚醒。在這里詳細(xì)描述了Unity的WWW類(lèi)不適合的詳細(xì)原因赴肚。
? ? ? ? 在編寫(xiě)自定義下載器時(shí)素跺,有三個(gè)選擇:
? ? ? ? ·C#的HttpWebRequest和WebClient類(lèi)
? ? ? ? ·自定義本機(jī)插件
? ? ? ? ·Asset store包
4.2.3.1.1 C#類(lèi)
? ? ? ? 如果應(yīng)用程序不需要HTTPS / SSL支持,則C#的WebClient類(lèi)提供了下載AssetBundles最簡(jiǎn)單的機(jī)制誉券。它能夠?qū)⑷魏挝募苯赢惒较螺d到本地存儲(chǔ)亡笑,而無(wú)需過(guò)多管理內(nèi)存分配。
? ? ? ? 要使用WebClient下載AssetBundle横朋,請(qǐng)分配該類(lèi)的一個(gè)實(shí)例仑乌,并傳遞用來(lái)下載AssetBundle的URL和一個(gè)目標(biāo)路徑。如果需要對(duì)請(qǐng)求參數(shù)進(jìn)行更多控制琴锭,可以使用C#的HttpWebRequest類(lèi)編寫(xiě)下載程序:
? ? ? ? 1.從HttpWebResponse.GetResponseStream獲取一個(gè)字節(jié)流晰甚。
? ? ? ? 2.在棧上分配一個(gè)固定大小的字節(jié)緩沖區(qū)。
? ? ? ? 3.將響應(yīng)流讀入緩沖區(qū)决帖。
? ? ? ? 4.使用C#的File.IO API或任何其他流式IO系統(tǒng)將緩沖寫(xiě)入磁盤(pán)厕九。
4.2.3.1.2 Asset Store包
? ? ? ? 多個(gè)Asset Store包提供通過(guò)HTTP,HTTPS和其他協(xié)議下載文件的本地代碼實(shí)現(xiàn)地回。在為Unity編寫(xiě)自定義本機(jī)代碼插件之前扁远,建議您先評(píng)估可用的Asset Store包俊鱼。
4.2.3.1.3 自定義原生插件
? ? ? ? 編寫(xiě)自定義本機(jī)插件是在Unity中下載數(shù)據(jù)最耗時(shí),但最靈活的方法畅买。由于編程時(shí)間要求高且技術(shù)風(fēng)險(xiǎn)高并闲,只有在沒(méi)有其他方法能夠滿足應(yīng)用程序要求的情況下才推薦此方法。例如谷羞,如果應(yīng)用程序必須在Unity中沒(méi)有C#SSL支持的平臺(tái)上使用SSL通信帝火,則可能需要自定義本機(jī)插件。
? ? ? ? 自定義本地插件通常會(huì)包裝目標(biāo)平臺(tái)的原生下載API湃缎。比如包括iOS上的NSURLConnection和Android上的java.net.HttpURLConnection犀填。請(qǐng)查閱每個(gè)平臺(tái)的原生文檔以獲取有關(guān)使用這些API的更多詳細(xì)信息。
4.2.3.2 存儲(chǔ)
? ? ? ? 在所有平臺(tái)上嗓违,Application.persistentDataPath指向一個(gè)可寫(xiě)的位置九巡,應(yīng)該用于存儲(chǔ)應(yīng)該在一個(gè)應(yīng)用程序運(yùn)行之間保持的數(shù)據(jù)。在編寫(xiě)自定義下載器時(shí)蹂季,強(qiáng)烈建議使用Application.persistentDataPath的子目錄來(lái)存儲(chǔ)下載的數(shù)據(jù)比庄。
? ? ? ? Application.streamingAssetPath不可寫(xiě),對(duì)于AssetBundle緩存是一個(gè)糟糕的選擇乏盐。
? ? ? ? streamingAssetsPath的示例位置包括:
? ? ? ? ·OSX:在.app包內(nèi); 不可寫(xiě)佳窑。
? ? ? ? ·Windows:在安裝目錄中(例如Program Files); 通常不可寫(xiě)
? ? ? ? ·iOS:在.ipa包內(nèi); 不可寫(xiě)
? ? ? ? ·Android:在.apk文件中; 不可寫(xiě)
4.3 Asset分配策略
? ? ? ? 決定怎樣劃分一個(gè)項(xiàng)目的Asset到AssetBundle中并不簡(jiǎn)單。很容易采用簡(jiǎn)單的策略父能,比如將所有Object放置在自己的AssetBundle中或僅使用一個(gè)AssetBundle神凑,但這些解決方案具有明顯的缺點(diǎn):
? ? ? ? ·擁有太少的AssetBundle:
? ? ? ? ? ? ? ? ——增加運(yùn)行時(shí)內(nèi)存占用量
? ? ? ? ? ? ? ? ——增加加載時(shí)間
? ? ? ? ? ? ? ? ——需要更大的下載量
? ? ? ? ·擁有太多的AssetBundle
? ? ? ? ? ? ? ? ——增加項(xiàng)目構(gòu)建時(shí)間
? ? ? ? ? ? ? ? ——可能使開(kāi)發(fā)復(fù)雜化
? ? ? ? ? ? ? ? ——增加總下載時(shí)間
? ? ? ? 關(guān)鍵是決定如何將Object分組為AssetBundle。主要策略是:
? ? ? ? ·邏輯實(shí)體
? ? ? ? ·Object類(lèi)型
? ? ? ? ·同時(shí)存在的內(nèi)容
? ? ? ? 有關(guān)這些分組策略的更多信息可以在手冊(cè)(https://docs.unity3d.com/Manual/AssetBundles-Preparing.html?_ga=2.23990394.1738950253.1523170803-288792143.1494236420)中找到何吝。
4.4 常見(jiàn)的陷阱
? ? ? ? 這個(gè)部分介紹項(xiàng)目中使用AssetBundles常見(jiàn)的幾個(gè)問(wèn)題溉委。
4.5.1 資產(chǎn)重復(fù)
? ? ? ? Unity5的AssetBundle系統(tǒng)將在Object構(gòu)建到AssetBundle中時(shí)發(fā)現(xiàn)Object的所有依賴關(guān)系。這個(gè)依賴關(guān)系信息用于確定包含在AssetBundle中的一組對(duì)象爱榕。明確分配給一個(gè)AssetBundle的Object只會(huì)構(gòu)建到該AssetBundle中瓣喊。當(dāng)Object的AssetImporter的assetBundleName屬性設(shè)置為非空字符串時(shí),對(duì)象將被“明確的分配” 黔酥。這可以在Unity Editor中通過(guò)在Object的監(jiān)視面板中選擇一個(gè)AssetBundle或從編輯器腳本中完成藻三。
? ? ? ? 也可以通過(guò)將Object定義為一個(gè)AssetBundleBuild映射的一部分來(lái)指定其AssetBundle,該映射與BuildPipeline.BuildAssetBundles()方法(接收處理一組AssetBundleBuild)的重載一起使用跪者。
? ? ? ? 未明確分配AssetBundle的所有Object將會(huì)包含在包含1個(gè)或多個(gè)引用了未標(biāo)記Object的Object的AssetBundle中棵帽。
? ? ? ? 例如,如果將兩個(gè)不同的Object分配給兩個(gè)不同的AssetBundles渣玲,但都引用到一個(gè)公共依賴的Object逗概,則該依賴Object將被復(fù)制到兩個(gè) AssetBundles中。重復(fù)的依賴關(guān)系也將被實(shí)例化忘衍,這意味著依賴Object的兩個(gè)副本將被視為具有不同標(biāo)識(shí)符的不同OBject逾苫。這將增加應(yīng)用程序AssetBundles的總大小卿城。如果應(yīng)用程序加載它的父項(xiàng),這也會(huì)導(dǎo)致Object的兩個(gè)不同副本被加載到內(nèi)存中铅搓。
? ? ? ? 有幾種方法可以解決這個(gè)問(wèn)題:
? ? ? ? 1.確保構(gòu)建到不同AssetBundles中的Object不共享依賴關(guān)系瑟押。任何共享依賴關(guān)系的對(duì)象都可以放置在同一個(gè)AssetBundle中,而不需要重復(fù)依賴關(guān)系狸吞。它產(chǎn)生的單個(gè)AssetBundles必須經(jīng)常重建和重新下載而造成不方便或低效勉耀。? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? 2.分割A(yù)ssetBundle指煎,以便不會(huì)同時(shí)加載共享依賴關(guān)系的兩個(gè)AssetBundle蹋偏。
? ? ? ?·此方法可能適用于某些類(lèi)型的項(xiàng)目,例如基于級(jí)別的游戲至壤。但是威始,它仍然會(huì)不必要地增加項(xiàng)目的AssetBundles的大小,并增加構(gòu)建時(shí)間和加載時(shí)間像街。
? ? ? ? 3.確保所有的依賴關(guān)系A(chǔ)sset被全部構(gòu)建進(jìn)自己的AssetBundle黎棠。這完全消除了重復(fù)資產(chǎn)的風(fēng)險(xiǎn),但也帶來(lái)了復(fù)雜性镰绎。應(yīng)用程序必須跟蹤AssetBundles之間的依賴關(guān)系脓斩,并確保在調(diào)用任何AssetBundle.LoadAsset相關(guān)API之前加載了正確的AssetBundle。
? ? ? ? 通過(guò)位于UnityEditor名稱空間下的AssetDatabase API可以跟蹤Object的依賴關(guān)系畴栖。正如命名空間所暗示的随静,這個(gè)API僅在Unity編輯器中可用,在運(yùn)行時(shí)不可用吗讶。
? ? ? ? AssetDatabase.GetDependencies可用于查找特定Object或Asset的所有直接依賴關(guān)系燎猛。請(qǐng)注意,這些依賴關(guān)系可能有其自己的依賴關(guān)系照皆。此外重绷,AssetImporter API可用于查詢?nèi)魏翁囟∣bject被分配的AssetBundle。
? ? ? ? 通過(guò)組合AssetDatabase和AssetImporter API膜毁,可以編寫(xiě)一個(gè)編輯器腳本昭卓,以確保所有AssetBundle的直接或間接依賴關(guān)系有被分配的AssetBundle,或者沒(méi)有兩個(gè)AssetBundle共享沒(méi)有被分配給一個(gè)AssetBundle的依賴關(guān)系瘟滨。由于重復(fù)Asset的內(nèi)存成本葬凳,建議所有項(xiàng)目都有這樣的腳本。
4.5.2 Sprite圖集重復(fù)
? ? ? ? 任何自動(dòng)生成的Sprite圖集將會(huì)被分配到包含生成Sprite圖集的Sprite對(duì)象的AssetBundle中室奏。如果Sprite對(duì)象被分配給多個(gè)AssetBundle火焰,那么Sprite圖集將不會(huì)被分配給一個(gè)AssetBundle而是將被復(fù)制。如果Sprite對(duì)象未分配給AssetBundle胧沫,則Sprite圖集也不會(huì)被分配給AssetBundle昌简。為了確保Sprite圖集沒(méi)有重復(fù)占业,請(qǐng)檢查標(biāo)記在同一個(gè)Sprite圖集的所有Sprite被分配到同樣的AssetBundle中。
? ? ? ? 請(qǐng)注意纯赎,在Unity 5.2.2p3及更早版本中谦疾,自動(dòng)生成的Sprite圖集將永遠(yuǎn)不會(huì)被分配給一個(gè)AssetBundle。因此犬金,它們將包含在所有包含組成它們的Sprite的AssetBundle中念恍,和任何引用到組成它們Sprite的AssetBundle中。由于這個(gè)問(wèn)題晚顷,強(qiáng)烈建議所有使用Unity的sprite packer的Unity 5項(xiàng)目升級(jí)到Unity 5.2.2p4,5.3或任何更新版本的Unity峰伙。
4.5.3 Android紋理
? ? ? ? 由于Android生態(tài)系統(tǒng)中設(shè)備碎片較多,通常需要將紋理壓縮為多種不同的格式该默。雖然所有Android設(shè)備都支持ETC1瞳氓,但ETC1不支持帶alpha通道的紋理。如果應(yīng)用程序不需要OpenGL ES 2支持栓袖,則解決該問(wèn)題的最干凈利落方法是使用所有Android OpenGL ES 3設(shè)備支持的ETC2匣摘。
? ? ? ? 大多數(shù)應(yīng)用程序需要在ETC2不支持的舊設(shè)備上安裝。解決此問(wèn)題的一種方法是使用Unity 5的AssetBundle變體(有關(guān)其他選項(xiàng)的詳細(xì)信息裹刮,請(qǐng)參閱Unity的Android優(yōu)化指南)音榜。
? ? ? ? 要使用AssetBundle變體,所有不能被ETC1干凈利落壓縮的紋理都要被分離到只有紋理的AssetBundle捧弃。接下來(lái)赠叼,使用特定供應(yīng)商的紋理壓縮格式(如DXT5,PVRTC和ATITC)塔橡,來(lái)創(chuàng)建關(guān)于這些AssetBundle足夠的變體來(lái)支持安卓生態(tài)系統(tǒng)的不能支持ETC2的切片梅割。對(duì)于每個(gè)AssetBundle變體,更改所包含紋理的TextureImporter設(shè)置來(lái)選擇一個(gè)適合變體的壓縮環(huán)境葛家。
? ? ? ? 在運(yùn)行時(shí)户辞,可以使用SystemInfo.SupportsTextureFormat API 檢測(cè)對(duì)不同紋理壓縮格式的支持。應(yīng)該使用此信息來(lái)選擇和加載支持格式的壓縮的紋理的AssetBundle變體癞谒。
? ? ? ? 有關(guān)Android紋理壓縮格式的更多信息可以在這里找到底燎。(鏈接見(jiàn)原網(wǎng)頁(yè))
4.5.4 iOS文件句柄過(guò)度使用
? ? ? ? Unity的當(dāng)前版本不受此問(wèn)題影響。
? ? ? ? 在Unity 5.3.2p2之前的版本中弹砚,Unity會(huì)對(duì)已經(jīng)加載的AssetBundle始終持有一個(gè)打開(kāi)的文件句柄双仍。這在大多數(shù)平臺(tái)上都不是問(wèn)題。但是桌吃,iOS平臺(tái)將進(jìn)程可以同時(shí)打開(kāi)的文件句柄數(shù)限制為255朱沃。如果加載AssetBundle導(dǎo)致超出數(shù)量限制,則這次加載調(diào)用將會(huì)失敗,并顯示“Too Many Open File Handles”錯(cuò)誤逗物。
? ? ? ? 對(duì)于嘗試將內(nèi)容分成數(shù)百或數(shù)千個(gè)AssetBundles的項(xiàng)目搬卒,這是一個(gè)常見(jiàn)問(wèn)題。
? ? ? ? 對(duì)于無(wú)法升級(jí)到補(bǔ)丁版本的Unity的項(xiàng)目翎卓,臨時(shí)解決方案是:
? ? ? ? ·通過(guò)合并相關(guān)的AssetBundles來(lái)減少使用的AssetBundles的數(shù)量
? ? ? ? ·使用AssetBundle.Unload(false)關(guān)閉AssetBundle的文件句柄契邀,并手動(dòng)管理加載的Object的生命周期
4.5 AssetBundle變體
? ? ? ? AssetBundle系統(tǒng)的一個(gè)關(guān)鍵特性是引入了AssetBundle變體。變體的目的是允許應(yīng)用程序調(diào)整其內(nèi)容以更好地適配其運(yùn)行時(shí)環(huán)境失暴。當(dāng)加載Object和解析Instance ID引用時(shí)坯门,變體允許不同的AssetBundle文件中的UnityEngine.Object顯示為“相同”對(duì)象。從概念上講逗扒,它允許兩個(gè)UnityEngine.Object顯示為共享相同的File GUID和Local ID古戴,并且通過(guò)一個(gè)字符串的變體ID來(lái)識(shí)別實(shí)際要加載的UnityEngine.Object。
? ? ? ? 這個(gè)系統(tǒng)有兩個(gè)主要用例:
? ? ? ? 1.變體簡(jiǎn)化了對(duì)于指定平臺(tái)的AssetBundle的加載適配缴阎。
? ? ? ? ·示例:構(gòu)建系統(tǒng)可以為一個(gè)獨(dú)立的Windows DirectX11應(yīng)用程序創(chuàng)建一個(gè)包含高分辨率紋理和復(fù)雜shader的AssetBundle的適配允瞧,同時(shí)為安卓創(chuàng)建第二個(gè)有低保真度內(nèi)容的AssetBundle简软。在運(yùn)行時(shí)蛮拔,項(xiàng)目的資源加載代碼可以為其平臺(tái)加載相應(yīng)的AssetBundle變體,并且傳遞到AssetBundle.Load API的Object名稱不需要更改痹升。
? ? ? ? 2.變體允許應(yīng)用程序在同一平臺(tái)上加載不同的內(nèi)容建炫,但應(yīng)用不同的硬件。
? ? ? ? ·這是支持各種移動(dòng)設(shè)備的關(guān)鍵疼蛾。在任何現(xiàn)實(shí)世界的應(yīng)用程序中肛跌,iPhone 4都不能像最新的iPhone一樣顯示相同保真度的內(nèi)容。
? ? ? ? ·在Android上察郁,AssetBundle變體可用于解決設(shè)備間屏幕縱橫比和DPI之間巨大的分裂問(wèn)題衍慎。
4.5.1 限制
? ? ? ? AssetBundle變體系統(tǒng)的一個(gè)關(guān)鍵限制是它需要使用不同的Asset來(lái)構(gòu)建變體。即使這些Asset之間的唯一差異是其導(dǎo)入設(shè)置也適用此限制皮钠。如果構(gòu)建到變體A和變體B中的紋理之間的唯一區(qū)別是在Unity紋理導(dǎo)入器中選擇的特定紋理壓縮算法稳捆,則變體A和變體B必須仍然是完全不同的資產(chǎn)。這意味著變體A和變體B必須是磁盤(pán)上的單獨(dú)文件麦轰。
? ? ? ? 這種限制使大型項(xiàng)目的管理復(fù)雜化乔夯,因?yàn)樘囟ˋsset的多個(gè)副本必須在源代碼中保持控制。當(dāng)開(kāi)發(fā)人員希望更改Asset的內(nèi)容時(shí)款侵,必須更新Asset的所有副本末荐。這個(gè)問(wèn)題沒(méi)有內(nèi)置的解決方法。
? ? ? ? 大多數(shù)團(tuán)隊(duì)都實(shí)施他們自己的AssetBundle變體形式新锈。這是通過(guò)在構(gòu)建AssetBundle時(shí)在文件名后添加良好定義的后綴來(lái)完成的甲脏,以便識(shí)別給定AssetBundle所代表的特定變體。在構(gòu)建這些AssetBundle時(shí),自定義代碼以編程方式更改包含的Asset的導(dǎo)入器設(shè)置块请。一些開(kāi)發(fā)者已經(jīng)擴(kuò)展了他們的自定義系統(tǒng)聋涨,以便能夠改變掛在prefab的組件上的參數(shù)。
4.6 壓縮還是不壓縮负乡?
? ? ? ? 是否壓縮AssetBundles需要一些重要的考慮因素牍白,其中包括:
? ? ? ? ·加載時(shí)間:從本地存儲(chǔ)或本地緩存加載時(shí),加載未壓縮的AssetBundles比加載壓縮的AssetBundles要快得多抖棘。
? ? ? ? ·構(gòu)建時(shí)間:在壓縮文件時(shí)LZMA和LZ4非常緩慢茂腥,并且Unity Editor按順序處理AssetBundle。具有大量AssetBundles的項(xiàng)目將花費(fèi)大量的時(shí)間壓縮它們切省。
? ? ? ? ·如果AssetBundles在應(yīng)用程序中安裝最岗,則壓縮它們將減少應(yīng)用程序的總大小。
? ? ? ? ·內(nèi)存使用情況:在Unity 5.3之前朝捆,所有Unity的解壓縮機(jī)制都要求在解壓縮之前將整個(gè)壓縮的AssetBundle加載到內(nèi)存中般渡。如果內(nèi)存使用率很重要,請(qǐng)使用未壓縮或LZ4壓縮的AssetBundles芙盘。
? ? ? ? ·下載時(shí)間:如果AssetBundles很大驯用,或者用戶處于帶寬受限的環(huán)境中,例如在低速或計(jì)量連接上下載儒老,那么壓縮可能是必須的選擇蝴乔。如果只有幾十MB的數(shù)據(jù)通過(guò)高速連接傳送到PC,則可以略壓縮驮樊。
4.6.1 Crunch壓縮
主要由使用Crunch壓縮算法的DXT壓縮紋理組成的包應(yīng)該被構(gòu)建為未壓縮的薇正。
4.7 AssetBundles和WebGL
? ? ? ? Unity強(qiáng)烈建議開(kāi)發(fā)人員不要在WebGL項(xiàng)目上使用壓縮的AssetBundle。WebGL項(xiàng)目中的所有AssetBundle解壓縮和加載必須在主線程上進(jìn)行囚衔。這是因?yàn)閁nity的WebGL導(dǎo)出選項(xiàng)當(dāng)前不支持工作線程挖腰。AssetBundle的下載被委托給了瀏覽器使用在Unity的主線程上執(zhí)行的XMLHttpRequest。這意味著壓縮的AssetBundles在WebGL上加載的消耗非常昂貴练湿。
? ? ? ? 如果您使用的是Unity 5.5或更早版本猴仑,請(qǐng)考慮避免在壓縮AssetBundle時(shí)使用LZMA,而是使用按需解壓縮非常效率的LZ4進(jìn)行壓縮作為代替鞠鲜。如果您需要更小的壓縮大小并使用LZ4進(jìn)行傳輸宁脊,您可以配置您的Web服務(wù)器在HTTP協(xié)議層面(在LZ4壓縮之上)對(duì)文件進(jìn)行g(shù)zip壓縮。Unity 5.6在WebGL平臺(tái)刪除了LZMA作為壓縮的選項(xiàng)贤姆。