之前有一篇文章中我們相惜討論了Assetbundle的原理,如果對原理還不太了解的朋友可以看這一篇文章:Unity游戲開發使用Assetbundle加載場景的原理 本篇文章我們將說說assetbundle是如何實現的。
1.創建Assetbundle
無論是模型資源還是UI資源,最好是先把他們放在Prefab中,然后在做成Assetbundle。我們以模型來舉例,Assetbundle中可以放一個模型、也可以放多個模型,它是非常靈活了那么最需要考慮的就是模型空間占用的問題。
比如我們有兩個完全一樣的模型,但是他們身上綁定的腳本不一樣,此時需要把這兩個模型放在兩個不同Prefab中。如下圖所示,我們分別對這兩個Prefab打包,我們可以清晰的看到兩個相同的Prefab打包在一起只占1M空間,而將他們分別打包會占1 + 1 = 2M空間。 Prefab在打包的同時會把模型身上的所有材質、貼圖、組件、腳本全部包含進去。
由此可得相同的模型盡量打包在一起,他們會公用一套資源文件。不相同的模型盡量分開打包,相同模型具有不同的腳本、組件的話把他們放在不同的Prefab中,最后把這些Prefab一起打包在一個Assetbundle中。如下圖所示,現在Project視圖中選擇需要打包的Prefab,然后在導航菜單欄中選擇Create Assetbundles Main表示分別打包、Create AssetBundles All表示將他們打包在一起。
這兩個prefab文件都指向了同一個模型,為了讓它倆有所區別,我給它倆綁定了相同的腳本,但是腳本中的參數是不同的。在編輯器上給每個Prefab賦值一個不同的名子,然后在Awake方法中進行輸出。
using UnityEngine; using System.Collections; public class Script : MonoBehaviour { public string name; void Awake () { Debug.Log("my name is "+ name); } }
Create Assetbundles Main : 分開打包,會生成兩個Assetbundle。
[MenuItem("Custom Editor/Create AssetBunldes Main")] static void CreateAssetBunldesMain () { //獲取在Project視圖中選擇的所有游戲對象 Object[] SelectedAsset = Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets); //遍歷所有的游戲對象 foreach (Object obj in SelectedAsset) { string sourcePath = AssetDatabase.GetAssetPath (obj); //本地測試:建議最后將Assetbundle放在StreamingAssets文件夾下,如果沒有就創建一個,因為移動平台下只能讀取這個路徑 //StreamingAssets是只讀路徑,不能寫入 //服務器下載:就不需要放在這里,服務器上客戶端用www類進行下載。 string targetPath = Application.dataPath + "/StreamingAssets/" + obj.name + ".assetbundle"; if (BuildPipeline.BuildAssetBundle (obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies)) { Debug.Log(obj.name +"資源打包成功"); } else { Debug.Log(obj.name +"資源打包失敗"); } } //刷新編輯器 AssetDatabase.Refresh (); }
最核心的方法其實就它:
BuildPipeline.BuildAssetBundle (obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies)
參數1:它只能放一個對象,因為我們這里是分別打包,所以通過循環將每個對象分別放在了這里。
參數2:可以放入一個數組對象。
默認情況下打的包只能在電腦上用,如果要在手機上用就要添加一個參數。
Android上:
BuildPipeline.BuildAssetBundle (obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies,BuildTarget.Android)
IOS上:
BuildPipeline.BuildAssetBundle (obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies,BuildTarget.iPhone)
另外,電腦上和手機上打出來的Assetbundle不能混用,不同平台只能用自己的。
Create AssetBundles All:將所有對象打包在一個Assetbundle中。
[MenuItem("Custom Editor/Create AssetBunldes ALL")] static void CreateAssetBunldesALL () { Caching.CleanCache (); string Path = Application.dataPath + "/StreamingAssets/ALL.assetbundle"; Object[] SelectedAsset = Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets); foreach (Object obj in SelectedAsset) { Debug.Log ("Create AssetBunldes name :" + obj); } //這里注意第二個參數就行 if (BuildPipeline.BuildAssetBundle (null, SelectedAsset, Path, BuildAssetBundleOptions.CollectDependencies)) { AssetDatabase.Refresh (); } else { } }
兩次打包完畢后,在StreamingAssets文件夾中就看到了這三個assetbundle文件。
2.讀取Assetbundle
然后我們來學習如何運行時讀取Assetbundle,Assetbundle是可以同時放在服務器或者本地的,無論放在哪里兩種下載讀取的方式是完全一樣的。所以我建議在做unity項目的時候開始就把資源放在Assetbundle中在本地來做,等做的差不多了直接把Assetbundle放在服務器上,因為兩種讀取的方式完全一樣,這樣以后更新資源會方便很多。然后是讀取,並且加載到游戲中。
using UnityEngine; using System.Collections; public class RunScript : MonoBehaviour { //不同平台下StreamingAssets的路徑是不同的,這里需要注意一下。 public static readonly string PathURL = #if UNITY_ANDROID "jar:file://" + Application.dataPath + "!/assets/"; #elif UNITY_IPHONE Application.dataPath + "/Raw/"; #elif UNITY_STANDALONE_WIN || UNITY_EDITOR "file://" + Application.dataPath + "/StreamingAssets/"; #else string.Empty; #endif void OnGUI() { if(GUILayout.Button("Main Assetbundle")) { StartCoroutine(LoadMainGameObject(PathURL + "Prefab0.assetbundle")); StartCoroutine(LoadMainGameObject(PathURL + "Prefab1.assetbundle")); } if(GUILayout.Button("ALL Assetbundle")) { StartCoroutine(LoadALLGameObject(PathURL + "ALL.assetbundle")); } } //讀取一個資源 private IEnumerator LoadMainGameObject(string path) { WWW bundle = new WWW(path); yield return bundle; //加載到游戲中 yield return Instantiate(bundle.assetBundle.mainAsset); bundle.assetBundle.Unload(false); } //讀取全部資源 private IEnumerator LoadALLGameObject(string path) { WWW bundle = new WWW(path); yield return bundle; //通過Prefab的名稱把他們都讀取出來 Object obj0 = bundle.assetBundle.Load("Prefab0"); Object obj1 = bundle.assetBundle.Load("Prefab1"); //加載到游戲中 yield return Instantiate(obj0); yield return Instantiate(obj1); bundle.assetBundle.Unload(false); } }
這里我們詳細的說說 下載類WWW
WWW bundle = new WWW(path);
這樣的做法是通過一個路徑進行下載(無論是服務器路徑還是本地路徑下載操作都一樣)但是bundle只能保存在內存中,也就是退出游戲在進入還得重新下,很顯然在游戲中我們不能使用這種方式。
private IEnumerator LoadMainCacheGameObject(string path) { WWW bundle = WWW.LoadFromCacheOrDownload(path,5); yield return bundle; //加載到游戲中 yield return Instantiate(bundle.assetBundle.mainAsset); bundle.assetBundle.Unload(false); }
使用的方法是WWW.LoadFromCacheOrDownload(path,5);
參數1:服務器或者本地下載地址
參數2:版本號
Unity會下載Assetbundle本地中,它的工作原理是先通過(版本號和下載地址)先在本地去找看有沒有這個Assetbundle,如果有直接返回對象,如果沒有的話,在根據這個下載地址重新從服務器或者本地下載。這里版本號起到了很重要的作用,舉個例子,同一下載地址版本號為1的時候已經下載到本地,此時將版本號的參數改成2 那么它又會重新下載,如果還保持版本號為1那么它會從本地讀取,因為本地已經有版本號為1的這個Assetbundle了。你不用擔心你的資源本地下載過多,也不用自己手動刪除他們,這一切的一切Unity會幫我們自動完成,它會自動刪除掉下載后最不常用的Assetbundle ,如果下次需要使用的話只要提供下載地址和版本后它會重新下載。
我們在聊聊Assetbundle 中的腳本,在移動平台下Assetbundle里面放的腳本是不會被執行的,還記得我們打包前給兩個Prefab掛上了腳本嗎?在手機上將Assetbundle下載到本地后,加載進游戲中Prefab會自動在本地找它身上掛着的腳本,他是根據腳本的名來尋找,如果本地有這條腳本的話,Prefab會把這個腳本重新綁定在自身,並且會把打包前的參數傳遞進來。如果本地沒有,身上掛的條腳本永遠都不會被執行。
在Prefab打包前,我在編輯器上給腳本中的變量 name 賦了不同值,當Prefab重新載入游戲的時候,它身上腳本的參數也會重新輸出。
如果你的Assetbundle中的Prefab上引用的對象,那么這樣做就會出錯了,你需要設定他們的依賴關系。或者運行時通過腳本動態的載入對象。
http://docs.unity3d.com/Documentation/ScriptReference/BuildPipeline.PopAssetDependencies.html
像這樣重新打包就可以。
3.打包場景
上面我們說過了打包Prefab,其實我們還可以把整個場景進行打包,因為移動平台不能更新腳本,所以這個功能就會有所限制,我的建議是烘培場景、然后把多個場景可復用的對象移除,場景中只保留獨一無二的游戲對象,然后在打包場景,運行游戲時載入場景后,在動態的將之前移除的對象重新添加進來。
[MenuItem("Custom Editor/Create Scene")] static void CreateSceneALL() { //清空一下緩存 Caching.CleanCache(); //獲得用戶選擇的路徑的方法,可以打開保存面板(推薦) string Path = EditorUtility.SaveFilePanel("保存資源", "SS", "" + "MyScene", "unity3d"); //另一種獲得用戶選擇的路徑,默認把打包后的文件放在Assets目錄下 //string Path = Application.dataPath + "/MyScene.unity3d"; //選擇的要保存的對象 string[] levels = { "Assets/Scenes/example.unity" }; //打包場景 BuildPipeline.BuildPlayer(levels, Path, BuildTarget.StandaloneWindows64, BuildOptions.BuildAdditionalStreamedScenes); // 刷新,可以直接在Unity工程中看見打包后的文件 AssetDatabase.Refresh(); }
不同平台下需要選擇 BuildTarget.Android 和 BuildTarget.iPhone 。 切記這段代碼是把example.unity常見文件打包到MyScene.unity3d文件中,所以在解包的時候也應當是先解開MyScene.unity3d,然后在去加載example.unity場景,無需在ProjectSetting中注冊新場景。
private IEnumerator LoadScene() { WWW download = WWW.LoadFromCacheOrDownload ("file://"+Application.dataPath + "/MyScene.unity3d", 1); yield return download; var bundle = download.assetBundle; Application.LoadLevel ("example"); }
在測試情況下你可能會頻繁的打包生成Assetbundle,如果忘記改版本號的話可能會讀取之前的緩存,可能就會看不到新的效果,所以我建議在bunild Assetbundle的時候強制清空一下緩存。
Caching.CleanCache();
最后點擊按鈕進行加載Assetbundle和 Scene吧。