第一次搞資源更新方面,這里只說更新,加載,AssetBundle資源加載,談談自己的理解,以及自己在項目中遇到的那些神坑,現在回想一下,真的是自己跪着過來的,說多了,都是淚。
我這邊是安卓AssetBundle資源加載。歡迎拍磚。
一.Unity中各個目錄
我這里說的是移動平台(安卓舉例),讀,寫。所謂讀,就是你出大版本的包之后,這個只讀的話,就一輩子就這些東西了,不會改變了,不會有其他資源來覆蓋或者增加啦。
可寫,就是可以加東西進去唄。可能是自己太笨,一開始沒怎么注意這意思。竟然往StreamingAssets去實現資源更新(天啦擼)。
Application.StreamingAssetsPath,
StreamingAssets目錄必須在Assets根目錄下,該目錄下所有資源也會被打包到游戲里,不同於Resources目錄,該目錄下的資源不會進行壓縮,同樣是只讀不可寫的。
這里的只可讀,不可寫,就是除了出大版本的包(重新下載),這里面的東西永遠不會變。
各平台StreamingAssets路徑打印:
Win:E:/myProj/Assets/StreamingAssets
Mac : /myProj/Assets/StreamingAssets
Andorid:jar:file:///data/app/com.myCompany.myProj-1/base.apk!/assets
iOS: /var/containers/Application/E5543D66-83F3-476D-8A8F-49D5332B3763/myProj.app/Data/Raw
Application.PersistentDataPath
應用程序安裝后才會出現。該目錄獨特之處在於是可讀,可寫的,所以我們一般將下載的AssetBundle存放於此。
各平台PersistentDataPath路徑打印:
Win:C:/Users/lodypig/Appdata/LocalLow/myCompany/myProj
Mac : /Users/lodypig/Library/Application Support/myCompany/myProj
Andorid:/data/data/com.myCompany.myProj/files
iOS: /var/mobile/Containers/Data/Appliction/A112252D-6B0E-459Z-9D49-CD3EAC6D47D/Documents
Application.DataPath
應用程序目錄,即Assets目錄。使用Appliction.dataPath訪問。只讀不可寫。
各平台DataPath路徑:
Win:E:/myProj/Assets
Mac : /myProj/Assets/
Andorid:/data/app/com.myCompany.myProj-1/base.apk!
iOS: /var/containers/Application/E5543D66-83F3-476D-8A8F-49D5332B3763/myProj.app/Data
綜上,也就是說,要實現資源更新,你只有把資源下載到Application.PersistentDataPath目錄下才可實現資源更新(增加或者替換),其他目錄不可能實現。
二.Unity游戲加載的資源是如何分配
首先你得有一個資源服務器(FTP為例),因為StreamingAssets目錄是只讀的,我們想要實現熱更新,StreamingAssets
目錄里面的東西一旦第一個版本打出APK的包之后,這里的東西將永遠不會變(只讀)。由於PersistentDataPath目錄是可讀可寫的,
所以游戲下載資源都會下載到這里面。這樣就實現了資源的熱更新。

注解:綠色的代表流動,可以不斷可以改變的資源。紅色線代表,讀取persistent目錄沒有的情況下,讀StreamingAssets目錄,所以,是永遠不變的資源。(除非你去重新下載一個apk的包,就不是熱更了)
三.如何加載本地的資源
首先優先判斷PersistentDataPath目錄下的資源是否存在,因為服務器上的資源都是下載到這里的,最新的資源通過下載到這里並且覆蓋,這里的資源
能保持跟服務器一致。(雨松之前講的UnityAssetBundle例子就是通過加載服務器上的,那個只是一個小案例,不能每次用哪下載到哪,每次都要下載,
這種方式是很不好用的,就第一次用的時候如果資源與服務器不一致,就下載到本地中,即PersistentDataPath目錄。)
因為每個游戲一開始出大版本的時候,都會附帶大量資源,就是放在StreamingAssets目錄,所以,這里存放大量資源。這樣減少下載的次數。
其實,換一種說法,PersistentDataPath完全是給StreamingAssets的補丁目錄,我是這樣理解的。當然,在項目運用中,都需要優先最先資源判斷。
四.遇到的那些神神神.....坑
(1) 不要以為在PC端可以加載的路徑,安卓也可以用。
我這邊因為涉及到WWW加載,貼出我的。
這里主要是通過www 如何加載PersistentDataPath和StreamingAssets這兩個目錄。
Application.PersistentDataPath:
/// <summary>
/// 天吶,一個Per目錄,還兩種方法加載。這真的是最后我找出來最完美的,可以加載的,之前還有N種版本,就不提了,網上有,我是經過實測,Unity5.3.4版本
/// 加載PC上安卓平台,加載PC上Standalone,加載安卓真機(APK包),這三個,都是可以加載的(WWW加載),
/// </summary>
public static string PERSISTENT_PATH_DATABASE //= LGameConfig.LOCAL_URL_PREFIX + Application.persistentDataPath + "/DataBase/";
{
get
{
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
return "file:///"+ Application.persistentDataPath + "/Test/";
#else
return "file://"+ Application.persistentDataPath + "/Test/";
#endif
}
}
Application.StreamingAssets:
public static string STREAMING_PATH_DATABASE
{
//這樣寫,因為安卓Unity平台是Application.isMobilePlatform==false, 而宏定義中又 ==UNITY_ANDROID。
//因為我們項目中是需要同時在PC下安卓平台和PC下 Standard平台,哈哈
get
{
if (!Application.isMobilePlatform)
{
return "file://" + Application.dataPath + "/StreamingAssets" + "/DataBase/";
}
else
{
return
#if UNITY_ANDROID
Application.streamingAssetsPath+ "/DataBase/";
#else
"file://" + Application.dataPath + "/StreamingAssets" + "/DataBase/";
#endif
}
}
這個加載地址,真的是精華,可能自己太笨,就是搞這個WWW加載安卓StreamingAssets目錄,花了大把時間,因為網上,加載方式真的是尼瑪一萬種,要噴一下,這些人,不實際打到APK測一下,我MDGB呀,坑的我好慘。
(2)不要以為在PC端可以用的方法,在安卓也可以用。
安卓上跟其他平台不一樣,安裝后,這些文件實際上是在一個Jar壓縮包里,所以不能直接用讀取文件的函數去讀,而要用WWW方式
讀取的代碼(假設名為"文件.txt")
(3) 安卓資源路徑加載,下載問題,真的是這次做AssetBundle最大的障礙。
(4)通過FTP和CDN下載資源的時候對應的 后綴是不同的。
FTP下載 后面用 "/" ,即可。
CDN下載 后面用 "//" ,即可。
(5)不同的加載方式,加載的路徑也是不同的。
具體我就不說了。
(6)記得加 加載文件的后綴名
安卓上跟其他平台不一樣,安裝后,這些文件實際上是在一個Jar壓縮包里,所以不能直接用讀取文件的函數去讀,而要用WWW方式
1.讀取的代碼(假設名為"文件.txt")
(7)加載方式,First In PersistentDataPath,Then StreamingAssets
IEnumerator LoadAnouncementText()
{
string strUrl = GetTxtPerststentUrl(anouncementText);
WWW www = new WWW(strUrl);
yield return www;
if (www.error == null)
{
mAnoucementText = ConvertByteToString(www.bytes);
}
else if (www.isDone)
{
string strPerUrl = GetTxtStreamUrl(anouncementText);
www = new WWW(strPerUrl);
yield return www;
if (www.error == null)
{
mAnoucementText = ConvertByteToString(www.bytes);
}
else if (www.isDone)
{
Debug.LogError("下載當前表出錯" + www.error.ToString());
}
}
}
string GetTxtStreamUrl(string name)
{
return STREAMING_PATH_DATABASE + name + ".txt";
}
string GetTxtPerststentUrl(string name)
{
return PERSISTENT_PATH_DATABASE + name + ".txt";
}
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!補充!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
補充:對於上面的路徑問題大家可能有些困惑。
貼出最詳細全面的路徑問題,經過測試,完全沒問題(安卓,PC都可以用,實際項目中使用)
string GetScenePath(string fileName) { string path =DataUrl.GetFilePersistentUrl(fileName)+".unity3d"; // string path= DataUrl.LOCAL_URL_PREFIX + Application.dataPath + "/StreamingAssets/" + "Scene_Main" + ".unity3d"; //讀取Per目錄的時候不需要加prefix,但是讀取Streaming目錄時候需加上prefix bool isPersistentDataPath = System.IO.File.Exists(path); if (!isPersistentDataPath) { path = DataUrl.GetFileStreamingUrl(fileName)+ ".unity3d"; if (!System.IO.File.Exists(path)) { Debug.LogError("Per,Stream目錄 scene bundle都不存在"); return null; } } return DataUrl.LOCAL_URL_PREFIX+ path; } DataUrl.cs public static string GetFilePersistentUrl(string path) { return Application.persistentDataPath+"/" + path; } public static string GetFileStreamingUrl(string path) { return Application.streamingAssetsPath+"/" + path; } #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN public static readonly string LOCAL_URL_PREFIX = "file:///"; #else public static readonly string LOCAL_URL_PREFIX="file://"; #endif
五.AssetBundle 加載方式
(轉自:https://blog.uwa4d.com/archives/ABTheory.html)
1.用法
AssetBundle加載資源分為兩步,第一步是獲取AssetBundle對象;第二步是通過該AssetBundle對象加載需要的資源。
第一步:獲取AssetBundle對象(可以分為以下兩種)
①先獲取WWW對象,再通過WWW.assetbundle來獲取AssetBundle對象。
public WWW(string url);
記載bundle文件並獲取WWW對象,完成后會在內存中創建較大的WebStream(解壓后的內容,通常為原Bundle文件的4~5倍,紋理資源可能更大),因此后續的AssetBundle.load可以直接在內存中進行。
public static WWW LoadFromCacheOrDownload(string url,int version,unit crc = 0);
加載Bundle文件並獲取WWW對象,同時將解壓形式的Bundle內容存入磁盤中作為緩存(如果該Bundle已經在緩存中,則省去這一步),完成后只會在內存中創建較小的SerializedFile,而后續的AssetBundle.load 需要通過IO從磁盤中的緩存中獲取。
綜上兩種方式,直接使用WWW.AssetBundle獲取AssetBundle對象。
②直接獲取AssetBundle。
public static AssetBundle LoadFromFile(string path);
通過未壓縮的Bundle文件,同步創建AssetBundle對象,這是最快的創建方式。創建完后只會在內存中創建較小的SerializedFile,
而后續的AssetBundle.Load需要通過IO從磁盤中獲取。
public static AssetBundleCreateRequest LoadFromMemoryAsync(byte[] binary);
通過Bundle的二進制數據,異步創建AssetBundle對象。完成后會在內存中創建較大的WebStream。調用時,Bundle的解壓是異步的。
public static LoadromMemory
上述方式的同步版本.
第二步:從AssetBundle加載資源的常用API
public T LoadAsset<T>(string name) where T: Object
2.Load assetBundle 區別
new WWW vs WWW.LoadFromCacheOrDownLoad
①前者的優勢
前者的Load操作在內存中進行,相比后者的IO操作開銷更小
不形成緩存文件,而后者則需要額外的磁盤空間存放緩存
②前者的劣勢
每次加載都涉及到解壓的操作,而后者在第二次加載時就省去了解壓的開銷
在內存中會有較大的WebStream,而后者在內存中通常只有較小的SerializedFile。
六.內存分析
WebStream:在使用new WWW或LoadFromMemory時產生,內存開銷較大
SerializedFile:內存開銷通常較小,但是一般磁盤中存儲資源,需要IO操作。
建議:
AssetBundle文件的大小不超過1MB,因為在普遍情況下Bundle加載時間與其大小並非呈線性關系,過大的Bundle可能引起較大的加載開銷。
由於WWW對象的加載是異步的,因此逐個加載容易出現CPU空閑的情況,此時建議適當同時加載多個對象,以增加CPU使用率,同時
加快加載的完成。
卸載:
場景物體(GameObject):這類物件可通過Destroy函數進行卸載。
資源(包括Prefab):除了Prefab以外,資源文件可以通過三種方式來卸載
1)Resource.UnLoadAsset 卸載指定的資源,CPU開銷小
2)Resource.UnLoadUnusedAssets:一次卸載所有未被引用的資源,CPU開銷大。
3)AssetBundle.UnLoad(true)在卸載AssetBundle對象時,所有該資源引用的資源也一起卸載,因為該方法容易造成資源丟失,不建議經常使用。unload(false),只卸載該資源。
4)WWW對象,調用對象的Dispose函數或將其置為null即可。
5)WebStream:在卸載WWW對象以及對應的AssetBundle對象后,這部分內存即會被引擎自動卸載。
6)SerializedFile:卸載AssetBundle后,這部分內存會被引擎自動卸載。
注意:
在通過AssetBundle.unload(false)卸載AssetBundle對象后,如果重新創建該對象並加載之前加載過的資源到內存時,會出現冗余,即兩份相同的資源。
被腳本的靜態變量引用的資源,在調用resource.unloadUnusedAssets,並不會被卸載。
推薦:
①對於需要常駐內存的Bundle文件來說,優先考慮減少內存占用,因此對於存放非Prefab資源(紋理)的Bundle文件,可以考慮使用LoadFromCacheOrDownLoad或LoadFromFile加載,從而避免WebStream常駐內存。對於存放較多Prefab資源的Bundle,則考慮使用WWW加載。因為這類Bundle用WWW.LoadFromCacheOrDownLoad加載時產生的SerializedFile可能會比WWW產生的WebStream更大。
②對於加載完后卸載Bundle文件,分兩種情況,優先考慮速度(加載場景時),優先考慮流暢度(游戲進行時)
加載場景的情況下,需要注意的是避免WWW對象的逐個加載導致的CPU空閑,優先考慮使用加載速度較快的LoadFromCacheDownLoad或LoadFromFile。
游戲進行時,需要避免使用同步操作引起卡頓,因此考慮使用WWW配合LoadAsync進行平滑的資源加載。
盡量避免游戲進行時調用Resource.UnLoadUnusedAssets().因為該接口開銷較大,容易造成卡頓,可以嘗試使用
Resource.UnLoad(obj)來逐個進行卸載,以保證游戲流暢度。
