工具
Unity 中的資源來(lái)源有三個(gè)途徑:一個(gè)是Unity自動(dòng)打包資源坤邪,一個(gè)是Resources盗痒,一個(gè)是AssetBundle膊夹。
Unity自動(dòng)打包資源是指在Unity場(chǎng)景中直接使用到的資源會(huì)隨著場(chǎng)景被自動(dòng)打包到游戲中击胜,這些資源會(huì)在場(chǎng)景加載的時(shí)候由unity自動(dòng)加載。這些資源只要放置在Unity工程目錄的Assets文件夾下即可甩恼,程序不需要關(guān)心他們的打包和加載,這也意味著這些資源都是靜態(tài)加載的沉颂。但在實(shí)際的游戲開(kāi)發(fā)中我們一般都是會(huì)動(dòng)態(tài)創(chuàng)建GameObject条摸,資源是動(dòng)態(tài)加載的,因此這種資源其實(shí)不多铸屉。 Resources資源是指在Unity工程的Assets目錄下面可以建一個(gè)Resources文件夾钉蒲,在這個(gè)文件夾下面放置的所有資源,不論是否被場(chǎng)景用到彻坛,都會(huì)被打包到游戲中顷啼,并且可以通過(guò)Resources.Load方法動(dòng)態(tài)加載。這是平時(shí)開(kāi)發(fā)是常用的資源加載方式昌屉,但是缺點(diǎn)是資源都直接打包到游戲包中了钙蒙,沒(méi)法做增量更新。 AssetBundle資源是指我們可以通過(guò)編輯器腳本來(lái)將資源打包成多個(gè)獨(dú)立的AssetBundle怠益。這些AssetBundle和游戲包是分離的仪搔,可以通過(guò)WWW類來(lái)加載。AssetBundle的使用很靈活:可以用來(lái)做分包發(fā)布蜻牢,例如大多數(shù)頁(yè)游資源是隨著游戲的過(guò)程增量下載的烤咧,或者有些手游資源過(guò)大,渠道要求發(fā)布的包限制在100M以內(nèi)抢呆,那只能把一開(kāi)始玩不到的內(nèi)容做成增量包煮嫌,等玩家玩到的時(shí)候通過(guò)網(wǎng)絡(luò)下載。AssetBundle 也可以用來(lái)做我們下面討論的自動(dòng)增量更新抱虐。Unity5相比之前的版本昌阿,AssetsBundle的打包過(guò)程有所簡(jiǎn)化。之前打包需要通過(guò)代碼來(lái)設(shè)置需要打入包的資源并自己建立包的依賴關(guān)系,Unity5可以通過(guò)每個(gè)資源Inspector底部的AssetBundle下拉來(lái)指定該資源要打入哪個(gè)包懦冰,不指定就是不打包灶轰。打包過(guò)程只需要BuildPipeline.BuildAssetBundles一句話就行了,Unity5會(huì)根據(jù)依賴關(guān)系自動(dòng)生成所有的包刷钢。每個(gè)包還會(huì)生成一個(gè)manifest文件笋颤,這個(gè)文件描述了包大小、crc驗(yàn)證内地、包之間的依賴關(guān)系等等伴澄,通過(guò)這個(gè)manifest打包工具在下次打包的時(shí)候可以判斷哪些包中的資源有改變,只打包資源改變的包阱缓,加快了打包速度非凌。manifest只是打包工具自己用的,發(fā)布包的時(shí)候并不需要荆针。
關(guān)于自動(dòng)生成依賴關(guān)系這個(gè)有必要提下敞嗡,Unity確實(shí)會(huì)自動(dòng)給你建立依賴關(guān)系,前提是你依賴的資源必須已經(jīng)在Inspector中設(shè)置了BundleName祭犯。如果沒(méi)有秸妥,Unity會(huì)把這個(gè)公用的資源重復(fù)打到多個(gè)用到的包中,因?yàn)檫@個(gè)公用資源不在一個(gè)獨(dú)立的包中沃粗,Unity也不會(huì)智能地給你發(fā)現(xiàn)它是公用的粥惧,然后生成一個(gè)公用包。更深的坑在于最盅,如果你公用的是一個(gè)FBX模型突雪,你只給這個(gè)模型設(shè)置BundleName還不行,它用到的貼圖涡贱,材質(zhì)都要設(shè)咏删,否則模型是公用了,貼圖沒(méi)有公用问词,結(jié)果貼圖還是被打包到多個(gè)包中了督函。所以設(shè)置BundleName這個(gè)工作最好還是由編輯器腳本來(lái)完成。
方案
Unity提供的就這些了激挪,下面就自己發(fā)揮:如何做一個(gè)方便的資源管理方案辰狡,既可以開(kāi)發(fā)時(shí)方便,又可以方便發(fā)布更新包呢垄分?開(kāi)發(fā)過(guò)程全用AssetsBundle是不合適的宛篇,因?yàn)殚_(kāi)發(fā)中資源經(jīng)常添加和更新,每次添加或者更新都生成一下AssetsBundle才能運(yùn)行是很麻煩的薄湿。而且我們要做的是自動(dòng)更新而不是分包下載叫倍,這也就是說(shuō)在發(fā)布游戲的時(shí)候這些資源應(yīng)該都是在游戲包中的偷卧,所以他們也不該從AssetsBundle加載。
分析完需求吆倦,方案也就出來(lái)了:資源還是放在Resources下面听诸,但是這些資源同時(shí)也會(huì)打包到AssetBundle中。代碼中所有加載資源的地方都通過(guò)自己的ResourceManager來(lái)加載逼庞,由ResourceMananger來(lái)決定是調(diào)用Resources.Load來(lái)加載資源還是從AssetsBundle加載蛇更。在開(kāi)發(fā)環(huán)境下(Editor)這些資源顯然是直接從Resources加載的瞻赶,發(fā)布的完整安裝包資源也是從Resources加載赛糟,只有當(dāng)有一個(gè)增量版本時(shí),游戲主程序才會(huì)去服務(wù)器把增量的AssetBundle下載下來(lái)砸逊,然后從AssetBundle加載資源璧南。
實(shí)現(xiàn)
實(shí)現(xiàn)中我們首先要考慮的是AssetBundle的粒度,即每個(gè)AssetBundle包含多少資源师逸。增量包的最小粒度就是AsssetBundle司倚, 如果單個(gè)AssetBundle過(guò)大,只要這個(gè)AssetBundle中有一個(gè)資源改變了就需要重新下載整個(gè)AssetBundle篓像,浪費(fèi)流量和玩家的等待時(shí)間动知;如果單個(gè)AssetBundle過(guò)小,極端情況是每個(gè)資源一個(gè)AssetBundle员辩,雖然實(shí)現(xiàn)了更新最小化盒粮,但是帶來(lái)了額外開(kāi)銷:AssetBundle本身也是有大小的,而且查找加載AssetBundle也是需要時(shí)間的奠滑。大家都往U盤(pán)里面拷過(guò)東西丹皱,拷一個(gè)1G的文件比拷1千個(gè)1M的文件要快很多。比較合理的做法是根據(jù)邏輯來(lái)宋税,例如每個(gè)角色可以有獨(dú)立的AssetBundle摊崭,公用的一些UI資源可以打到一個(gè)AssetBundle里面,每個(gè)場(chǎng)景獨(dú)立的UI資源可以打成獨(dú)立的AssetBundle杰赛。這樣做資源預(yù)加載的時(shí)候也方便呢簸,每個(gè)場(chǎng)景需要用到幾個(gè)Bundle就加載幾個(gè)Bundle,無(wú)關(guān)的資源不會(huì)被加載。
下面要考慮的是如何來(lái)確定一個(gè)資源是從Resources加載還是AssetBundle加載乏屯。為此我們需要一個(gè)配置文件resourcesinfo根时。這個(gè)文件隨打包過(guò)程自動(dòng)生成。里面包含了資源版本號(hào)version瓶珊,所有包的名字啸箫,每個(gè)包的HashCode以及每個(gè)包里面包含的資源的名字。HashCode直接可以從Unity生成的manifest中得到(AssetBundleManifest.GetAssetBundleHash)伞芹,用來(lái)檢查包的內(nèi)容是否發(fā)生變化忘苛。這個(gè)resourceinfo每次打包AssetBundle時(shí)都會(huì)生成一個(gè)蝉娜,發(fā)布增量時(shí)將它和新的Bundle一起全部復(fù)制到服務(wù)器上。同時(shí)在Resources文件夾下也存一份扎唾,隨完整安裝包發(fā)布召川,這就保證了新安裝游戲的玩家手機(jī)上也有一份完整的資源配置文件,記錄了這個(gè)完整包包含的資源胸遇。
當(dāng)游戲啟動(dòng)時(shí)荧呐,首先請(qǐng)求服務(wù)器檢查版本號(hào),前端用的版本號(hào)就是Resources下面的這個(gè)resourcesinfo中的version纸镊。服務(wù)器比對(duì)這個(gè)版本號(hào)來(lái)告訴前端是否需要更新倍阐。如果需要更新,前端就去獲取服務(wù)器端的新resourcesinfo逗威,然后比對(duì)里面每個(gè)bundle的HashCode峰搪,把HashCode不同的bundle記錄下來(lái),然后通過(guò)WWW類來(lái)下載這些發(fā)生改變的bundle凯旭,當(dāng)然如果服務(wù)器版的resourcesinfo中包含了本地resourceinfo中所沒(méi)有的Bundle概耻,這些Bundle就是新增的,也需要下載下來(lái)罐呼。所有下載完成后鞠柄,前端將這個(gè)新的resourceinfo保存到本地存儲(chǔ)中,后面前端的所有操作都將以這個(gè)resourceinfo為準(zhǔn)而不再是Resources下面的resourceinfo了嫉柴。Resources下的resourceinfo可以退出歷史舞臺(tái)了厌杜,除非一種情況:本地存儲(chǔ)的resourceinfo被認(rèn)為刪除了。手機(jī)端玩家清理應(yīng)用的數(shù)據(jù)就會(huì)造成下載的bundle以及resourceinfo被刪除差凹。沒(méi)關(guān)系期奔,這時(shí)候前端由于找不到外部的resourceinfo了,還會(huì)使用Resources下面的resourceinfo和服務(wù)器比對(duì)危尿,把新的bundle重新下載下來(lái)呐萌。
現(xiàn)在從哪里加載資源就很明確了:ResourceMananger先讀取resourcesinfo,知道了游戲中所有的Bundle和每個(gè)Bundle包含的資源谊娇,然后去外部存儲(chǔ)查找這些Bundle是否存在肺孤,如果存在,就記錄下這個(gè)Bundle的資源應(yīng)該從外部的AssetBundle加載济欢,如果不存在赠堵,就從內(nèi)部的Resources加載。在開(kāi)發(fā)過(guò)程(Editor)中法褥,由于不存在外部存儲(chǔ)的bundle茫叭,資源自然都是從Resources加載的,達(dá)到了我們開(kāi)發(fā)方便的目的半等。這個(gè)過(guò)程隱含了一點(diǎn):不是所有的資源都需要有BundleName而被打包到AssetBundle中揍愁,游戲內(nèi)不需要后續(xù)更新的資源就不要設(shè)置BundleName呐萨,它們不會(huì)被打包更新,這樣的資源ResourceManager在resourceinfo中是找不到的莽囤,直接去Resources文件夾下面讀取就行了谬擦。
加載AssetBundle,我們直接使用WWW類而不用WWW.LoadFromCacheOrDownload, 因?yàn)槲覀兊馁Y源在游戲開(kāi)始的時(shí)候已經(jīng)下載到外部存儲(chǔ)了朽缎,不要再Download也不要再Cache惨远。注意WWW類加載是異步的,在游戲中我們需要同步加載資源的地方就要注意把資源預(yù)加載好存在ResourceManager中话肖,不然等用的時(shí)候加載肯定要寫(xiě)異步代碼了北秽。大部分時(shí)候我們應(yīng)該在一個(gè)場(chǎng)景初始化時(shí)就預(yù)加載好所有資源,用的時(shí)候直接從ResourceManager的緩存取就可以了狼牺。
資源加載卸載
最后簡(jiǎn)單說(shuō)下資源的加載卸載羡儿,這個(gè)網(wǎng)上也有很多文章介紹。 從我理解來(lái)看Resources是一個(gè)缺省自動(dòng)打包的特殊AssetBundle是钥。無(wú)論從WWW還是AssetBundle.CreateFromFile創(chuàng)建AssetBundle其實(shí)是創(chuàng)建了一個(gè)文件內(nèi)存鏡像。這時(shí)候是沒(méi)有Asset的缅叠。AssetBundle.LoadAsset 和Resource.Load才真正創(chuàng)建出了Asset悄泥,而Instaniate復(fù)制了這個(gè)Asset。注意這個(gè)復(fù)制有兩種肤粱,學(xué)C++的都知道淺拷貝和深拷貝弹囚,這里的復(fù)制有的是正真的復(fù)制,有的是引用领曼。為什么要這樣呢鸥鹉?因?yàn)橛行┯螒蛸Y源是只讀的,像貼圖Texture庶骄,這么大而且只讀毁渗,當(dāng)然不需要再去完全復(fù)制一份。但像GameObject這種資源它的屬性是可以通過(guò)腳本改變的单刁,必須要復(fù)制一份灸异。所以一個(gè)資源從AssetBundle到場(chǎng)景中被實(shí)例化,其實(shí)有3塊內(nèi)存被創(chuàng)建羔飞,這3快內(nèi)存的釋放是有不同方法的肺樟。
文件內(nèi)存鏡像是通過(guò)AssetBundle.Unload(false)來(lái)釋放的。 Instaniate出來(lái)的Object內(nèi)存通過(guò)Object.Destory來(lái)釋放逻淌。 AssetBundle.Unload(true)不單會(huì)釋放文件內(nèi)存鏡像么伯,還會(huì)釋放AssetBundle.Load創(chuàng)建的Assets。這個(gè)方法是不安全的卡儒,除非你能保證這些Assets沒(méi)有Object在引用田柔,否則就出問(wèn)題了誓篱。 Resources.UnloadAsset和Resources.UnloadUnusedAssets可以用來(lái)釋放Asset。下面這個(gè)圖很直觀:
這是老Unity的圖凯楔,Unity5已經(jīng)把AssetBundle.Load 改成了AssetBundle.LoadAsset窜骄。這個(gè)改動(dòng)讓我們更明確了Load出來(lái)的是Asset這塊內(nèi)存區(qū)域。什么時(shí)候把Resource.Load也改了吧摆屯。
注意事項(xiàng)(坑)
Resources.Load方法傳入的資源路徑需是從Resources文件夾下一級(jí)開(kāi)始的相對(duì)路徑且不能包含擴(kuò)展名邻遏;而AssetBundle.LoadAsset方法傳入的資源名需是從Assets文件開(kāi)始的全路徑且要包含擴(kuò)展名。路徑不區(qū)分大小寫(xiě)虐骑,建議全用小寫(xiě)准验,因?yàn)锳ssetBundle.GetAllAssetNames方法返回的資源名都是小寫(xiě)的。 Unity5打包AssetBundle時(shí)會(huì)自動(dòng)處理依賴關(guān)系廷没,但是在運(yùn)行時(shí)加載的時(shí)候卻不會(huì)糊饱,程序需要自己處理,先加載依賴包颠黎。 AssetBundle.CreateFromFile不能加載壓縮過(guò)的AssetBundle另锋,所以我們只能用WWW來(lái)異步加載AssetBundle。 目前我用的Unity5.0.2f1的Resources.Load方法在手機(jī)端比原來(lái)慢了很多狭归,如果以前可以不緩存每次用的時(shí)候都調(diào)用Resource.Load現(xiàn)在就不行了夭坪。頻繁的調(diào)用會(huì)導(dǎo)致明顯的性能開(kāi)銷,不知道是不是Bug过椎。原文地址:http://blog.csdn.net/ring0hx/article/details/46376709