首页 > 代码库 > Assetbundle的杂七杂八
Assetbundle的杂七杂八
使用Assetbundle时可能遇到的坑
一 24
转自 http://www.unitymanual.com/blog-3571-132.html
1.Editor版本不能读取与自己版本不同的assetbundle
这个问题描述起来很简单:比如:老板原来让你用4.1打包(BuildAssetBundle)开发,开发完毕后,下半年,unity升级了, 于是老板要求与时俱进,让你用4.3开发维护,这时,问题就出现了:4.1下的editor打包的assetbundle在editor下不能被4.3读 取,会报错。
那么解决方法是啥呢?把所有资源重新在4.3下打包。很坑吧?但是,这问题仅仅只是在editor下会出现,webplayer不会出现。但你恐怕很少说调试程序不走editor吧?
2.assetbundle如果从WWW中被读取过一次,再读取会报错
当你把assetbundle资源用www下载下来后,往往都会使用“wwwResource.assetbundle”,但是注意,这个函数调用assetbundle的时候,只能调用一遍。意思是,当你要再次读取的时候,会报错。这时候,需要你写一套对于assetbundle的控制程序,保证第一次读取资源的时候是使用wwwResource.assetbundle,第二次再次利用,则要用已经读取出来的资源。
3.慎用BuildPipeline的PushAssetDependencies()和PopAssetDependencies()功能
在我们的项目中,打包时,用到了BuildPipeline.PushAssetDependencies()和 BuildPipeline.PopAssetDependencies()的功能,这是unity里比较好的一项功能,它可以把unity里的依赖关系 比较好的保存下来,并正常读取就可以还原。比如:一个模型用到了贴图1,贴图2,贴图3,这时,我们可以用这种依赖关系的方式,把模型单独打出来,同时把 贴图1贴图2贴图3也单独打包出来,这样的话,如果有别的模型也用到了贴图123,只需要下载一次,然后在读进工程的时候,贴图自动找到模型,并贴上去。 听起来很美,但是也有问题:严格根据依赖关系按顺序下载资源。否则,留给你的是一个个白模。我们被这个问题坑了好久。
4.Unity编译的webplayer工程,免费cache只有50MB
听起来是不是好惊讶?webplayer下的免费cache居然这么少?意味着,在webplayer下,你的LoadFromCacheOrDownload用处并不是非常大。因为你的资源需要一遍又一遍的下载,cache的作用大大缩小了。而如果你想扩展,需要联系他们的销售部,据说价格不菲。不过针对这个问题,现在有人已经利用游览器的缓存绕过了这个问题,思路比较巧妙,但是涉及到unity官方利益,我在这里就不介绍了。
在IOS平台加载Assetbundle的注意事项
十一 28
1、Assetbundle打包时需要使用 BuildTarget.iPhone 参数。不同发布平台打包的文件是不通用的。
2、当上传已经打包好的文件到FTP服务器时,注意在上传软件菜单里选择传输类型为二进制格式,而不是默认的 ASCII 格式。
AssetBundles (Pro only)
AssetBundles are files which you can export from Unity to contain assets of your choice. These files use a proprietary compressed format and can be loaded on demand by your application. This allows you to stream in content, such as models, textures, audio clips, or even entire scenes separately from the scene in which they will be used. AssetBundles have been designed to simplify downloading content to your application. AssetBundles can contain any kind of asset type recognized by Unity, as determined by the filename extension. If you want to include files with custom binary data, they should have the extension ".bytes". Unity will import these files as TextAssets.
When working with AssetBundles, here‘s the typical workflow:
During development, the developer prepares AssetBundles and uploads them to a server.
- Building AssetBundles. Asset bundles are created in the editor from assets in your scene. The Asset Bundle building process is described in more detail in the section for Building AssetBundles
- Uploading AssetBundles to external storage. This step does not include the Unity Editor or any other Unity channels, but we include it for completeness. You can use an FTP client to upload your Asset Bundles to the server of your choice.
At runtime, on the user‘s machine, the application will load AssetBundles on demand and operate individual assets within each AssetBundle as needed.
Downloading AssetBundles and loading assets from them
- Downloading AssetBundles at runtime from your application. This is done from script within a Unity scene, and Asset Bundles are loaded from the server on demand. More on that in Downloading Asset Bundles.
- Loading objects from AssetBundles. Once the AssetBundle is downloaded, you might want to access its individual Assets from the Bundle. More on that in Loading Resources from AssetBundles
Please read this section of the documentation thoroughly to familiarize yourself with the workflow for using AssetBundles, discover the different features they provide and learn best practices that can save you time and effort during development.
See also:
- Frequently Asked Questions
- Building AssetBundles
- Downloading Asset Bundles
- Loading Asset Bundles
- Keeping track of loaded AssetBundles
- Storing and loading binary data
- Protecting content
- Managing Asset Dependencies
- Including scripts in AssetBundles
Building AssetBundles
There are three class methods you can use to build AssetBundles:
- BuildPipeline.BuildAssetBundle allows you to build AssetBundles of any type of asset.
- BuildPipeline.BuildStreamedSceneAssetBundle is used when you want to include only scenes to be streamed and loaded as the data becomes available.
- BuildPipeline.BuildAssetBundleExplicitAssetNames is the same as BuildPipeline.BuildAssetBundle but has an extra parameter to specify a custom string identifier (name) for each object.
An example of how to build an AssetBundle
Building asset bundles is done through editor scripting. There is basic example of this in the scripting documentation for BuildPipeline.BuildAssetBundle.
For the sake of this example, copy and paste the script from the link above into a new C# script called ExportAssetBundles. This script should be placed in a folder named Editor, so that it works inside the Unity Editor.
Now in the
For this example, you should create a new prefab. First create a new Cube by going to
You should then right click the Cube prefab in the project window and select
At this point you can move the AssetBundle Cube.unity3d elsewhere on your local storage, or upload it to a server of your choice.
An example of how to change the properties of the assets when building an Asset Bundle
You can use AssetDatabase.ImportAsset to force reimporting the asset right before calling BuildPipeline.BuildAssetBundle, and then use AssetPostprocessor.OnPreprocessTexture to set the required properties. The following example will show you how to set different texture compressions when building the Asset Bundle.
C#
// Builds an asset bundle from the selected objects in the project view,// and changes the texture format using an AssetPostprocessor.using UnityEngine;using UnityEditor;public class ExportAssetBundles { // Store current texture format for the TextureProcessor. public static TextureImporterFormat textureFormat; [MenuItem("Assets/Build AssetBundle From Selection - PVRTC_RGB2")] static void ExportResourceRGB2 () { textureFormat = TextureImporterFormat.PVRTC_RGB2; ExportResource(); } [MenuItem("Assets/Build AssetBundle From Selection - PVRTC_RGB4")] static void ExportResourceRGB4 () { textureFormat = TextureImporterFormat.PVRTC_RGB4; ExportResource(); } static void ExportResource () { // Bring up save panel. string path = EditorUtility.SaveFilePanel ("Save Resource", "", "New Resource", "unity3d"); if (path.Length != 0) { // Build the resource file from the active selection. Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets); foreach (object asset in selection) { string assetPath = AssetDatabase.GetAssetPath((UnityEngine.Object) asset); if (asset is Texture2D) { // Force reimport thru TextureProcessor. AssetDatabase.ImportAsset(assetPath); } } BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets); Selection.objects = selection; } }}
C#
// Changes the texture format when building the Asset Bundle.using UnityEngine;using UnityEditor;public class TextureProcessor : AssetPostprocessor{ void OnPreprocessTexture() { TextureImporter importer = assetImporter as TextureImporter; importer.textureFormat = ExportAssetBundles.textureFormat; }}
You can also control how the asset is imported using the AssetDatabase.ImportAssetOptions.
In a test environment, you sometimes need to test a change that require AssetBundles to be rebuilt. In these cases, it is advisable to use the option BuildAssetBundleOptions.UncompressedAssetBundle when you build the AssetBundles. This makes it faster to build and load the AssetBundles but they will also be bigger and therefore take longer to download.
Building AssetBundles in a production enviroment
When first using AssetBundles it may seem enough to manually build them as seen in the previous example. But as a project grows in size and the number of assets increases doing this process by hand is not efficient. A better approach is to write a function that builds all of the AssetBundles for a project. You can, for example, use a text file that maps Asset files to AssetBundle files.
back to AssetBundles Intro
Streaming Assets
Application.dataPath
Contains the path to the game data folder (Read Only).
Unity Editor: <path to project folder>/Assets
Mac player: <path to player app bundle>/Contents
iPhone player: <path to player app bundle>/<AppName.app>/Data
Win player: <path to executablename_Data folder>
Web player: The absolute url to the player data file folder (without the actual data file name)
Flash: The absolute url to the player data file folder (without the actual data file name)
Note that the string returned on a PC will use a forward slash as a folder separator.
Application.streamingAssetsPath
Contains the path to the StreamingAssets folder (Read Only).
Any files placed in a folder called StreamingAssets in a Unity project will be copied verbatim to a particular folder on the target machine.
It‘s always best to use Application.streamingAssetsPath to get the location of the StreamingAssets folder, it will always point to the correct location on the platform where the application is running.
Note that on some platforms it is not possible to directly access the StreamingAssets folder because there is no file system access in the web platforms, and because it is compressed into the .apk file on Android. On those platforms, a url will be returned, which can be used using the WWW class.
// print the path to the streaming assets folder var filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "MyFile"); var result = ""; if (filePath.Contains("://")) { var www = new WWW (filePath); yield www; result = www.text; } else result = System.IO.File.ReadAllText(filePath);
You can retrieve the folder using theApplication.streamingAssetsPath property. For reference, the location of this folder varies per platform:
On a desktop computer (Mac OS or Windows) the location of the files can be obtained with the following code:
path = Application.dataPath + "/StreamingAssets";
On iOS, you should use:
path = Application.dataPath + "/Raw";
...while on Android, you should use:
path = "jar:file://" + Application.dataPath + "!/assets/";
Note that on Android, the files are contained within a compressed .jar file (which is essentially the same format as standard zip-compressed files). This means that if you do not use Unity‘s WWW class to retrieve the file then you will need to use additional software to see inside the .jar archive and obtain the file.
Unity 3D : 製作與載入 AssetBundle
通常我們在遊戲程式執行過程,並不希望一次將全部的資源都載入,而比較希望實際上有使用到的才載入,以免佔用多餘的記憶體,所以我們可能會儘量規劃好不同功能的場景,在需要時才載入場景並釋放掉前個場景中不需要的資源,或是將資源放在 Resource 資料夾中,在真正需要時才利用 Resources.Load() 把資源載入;這些都是不錯的管理方法,但是當我們遊戲中的資源相當多時,輸出的程式還是會相當龐大,而且如果是時常會更新內容資源的遊戲,也會因為龐大的資源而造成編譯輸出時要花相當多的時間;特別是手機或網頁遊戲,幾乎輸出最後的結果只是一個檔案而已,這個檔案如果很龐大,將可能造成下載或啟動遊戲的不方便;這時候我們可能就要考慮製作 AssetBundle。
製作與遊戲主程式分離的資源包可以得到某程度上的好處,我們可以讓遊戲主程式只管理遊戲邏輯的部份,當有需要某些資源則從外部的 AssetBundle 載入資源,這樣一來,如果有任何遊戲更新只是添加或更換資源,並沒有變更到遊戲程式邏輯或內容部署,我們只需要重新打包資源的部份就能完成更新,而不再需要整個遊戲重新編譯輸出。
例如我們有已上架的 iPhone 遊戲,因為特定節日想把遊戲內的圖片換成另一種氣氛,通常就必須要重新輸出,然後送審,等節日過後又想把圖片換回原本的,要再次輸出遊戲,再送審,一方面送審費時費工,另一方面是從送審到架上遊戲更新完成的時間,我們並不能準確掌控;如果事先將資源分離打包成 AssetBundle 儲存在我們自己管理的伺服器中,在這種需要換圖的時刻,只需要打包自行在伺服器中將 AssetBundle 更新即可,可以省掉不斷送審的麻煩,時間上也更能掌控,另外就是遊戲主程式中沒有龐大的資源資料,主程式也就會小一點,那麼玩家要下載我們的遊戲也會更快速些。
還有就是,我們輸出為網頁遊戲時一定會發現到,輸出結果只是一個 html 檔及一個 unity3d 檔,應該有寫過網頁的人都知道,網頁的運作是瀏覽器將網頁中的文件、圖片、音樂、影像... 等等的資料下載到客戶端暫存之後再執行呈現出結果,那麼如果我們遊戲內容的龐大資源都與主程式編在一起的話,那麼輸出後的 unity3d 檔應該也會不小,此時我們可能要思考一下,我們希望玩家花多久時間完成啟動頁面再進行遊戲呢?也許 Unity 的 Web Player 有辦法解決這個問題使玩家不會花太多時間啟動頁面(這部份我就比較不確定了^_^),但是正因為網頁的運作方式是如此,所以當我們在伺服器端更新時,玩家只要沒有重新載入頁面,玩到的遊戲內容始終是舊版的,此時又要考慮到,如果沒有更新遊戲邏輯或內容規則的情況下,是否有必要強制玩家中斷遊戲重新載入頁面;如果將資源都打包成 AssetBundle 分離出來,那麼 unity3d 檔這個主程式的部份將會變得更小,而當有任何資源更新時只需要在伺服器端將 AssetBundle 的檔案換掉,玩家不需要重新載入頁面一樣能體驗到更新後的內容。
以上寫了一大堆,主要就是要說明 AssetBundle 是個很重要又好用的東西,畢竟在沒變更程式或遊戲部署的情況下,沒必要去重新 Build 遊戲主程式,以下就來說明如何製作 AssetBundle ...
首先要使用【自訂 Unity 工具列選單處理專案內容】的方法自定義選單命令,使我們能夠操作何時開始製作 AssetBundle 及如何做,接下來我們需要能夠指定哪些資源將打包成 AssetBundle,所以需要使用到 Editor class 的 Selection.GetFiltered(),還有就是要過濾哪些型別的資源才是我們要的,選擇錯誤則略過,不要處理,最後再使用 BuildPipeline.BuildAssetBundle() 建立 AssetBundle 即可,以下範例會在 Unity 專案資料夾(不是 Asset 資料夾)中建立 _AssetBunldes 資料夾用來存放打包好的 AssetBundle,打包的目標為 GameObject、Texture2D、Material 三種型別的資源,如果儲存檔案路徑檔名相同則會先刪除舊檔:
[MenuItem("Custom Editor/Create AssetBunldes")]
static void ExecCreateAssetBunldes(){
// AssetBundle 的資料夾名稱及副檔名
string targetDir = "_AssetBunldes";
string extensionName = ".assetBunldes";
//取得在 Project 視窗中選擇的資源(包含資料夾的子目錄中的資源)
Object[] SelectedAsset = Selection.GetFiltered(typeof (Object), SelectionMode.DeepAssets);
//建立存放 AssetBundle 的資料夾
if(!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir);
foreach(Object obj in SelectedAsset){
//資源檔案路徑
string sourcePath = AssetDatabase.GetAssetPath(obj);
// AssetBundle 儲存檔案路徑
string targetPath = targetDir + Path.DirectorySeparatorChar + obj.name + extensionName;
if(File.Exists(targetPath)) File.Delete(targetPath);
if(!(obj is GameObject) && !(obj is Texture2D) && !(obj is Material)) continue;
//建立 AssetBundle
if(BuildPipeline.BuildAssetBundle(obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies)){
Debug.Log(obj.name + " 建立完成");
}else{
Debug.Log(obj.name + " 建立失敗");
}
}
}
以上是直接將選擇的資源打包成 AssetBundle,另外,你也可以在打包前依需求將型別為 GameObject 的資源 Instantiate 到場景中添加需要的 Component 並回到 Project 中建立 Prefab,最後再以這個 Prefab 打包成 AssetBundle,一切就看個人如何運用了。
製作好的 AssetBundle 最終目的還是要在遊戲中載入,以下用簡短的範例在遊戲畫面上顯示一個按鈕,當按下按鈕之後載入型別為 GameObject 的 AssetBundle 並利用 Instantiate() 使其實例化而在場景中產生 GameObject:
void OnGUI(){
if(GUI.Button(new Rect(5,35,100,25) , "Load GameObject")){
StartCoroutine(LoadGameObject());
}
}
private IEnumerator LoadGameObject(){
// AssetBundle 檔案路徑
string path = string.Format("file://{0}/../_AssetBunldes/{1}.assetBunldes" , Application.dataPath , "TestGameObject");
// 載入 AssetBundle
WWW bundle = new WWW(path);
//等待載入完成
yield return bundle;
//實例化 GameObject 並等待實作完成
yield return Instantiate(bundle.assetBundle.mainAsset);
//卸載 AssetBundle
bundle.assetBundle.Unload(false);
}
範例中的檔案路徑(path)字串是以此程式碼在 Unity 編輯器中執行為例,實作時應該以實際執行平台及個人喜好指定 AssetBundle 存放路徑,詳情可參考官方文件Application.dataPath 說明;因為載入的資源必須要等待載入完成以確保遊戲運作正常,在 C# 使用 yield 除了回傳型別為 IEnumerator 之外,調用時也必須使用 StartCoroutine() 調用,如果使用 Javascript 則可直接呼叫該 function;如果 AssetBundle 已載入過,下次再請求載入將會出現 【The asset bundle ‘檔名‘ can‘t be loaded because another asset bundle with the same files are already loaded】的錯誤訊息,即使使用區域變數(範例中的 bundle)仍然會得到這個錯誤訊息,所以必須在每次載入使用完後將 AssetBundle 卸載;另外,要特別注意的是載入 AssetBundle 到卸載期間可能消耗些微時間,如果連續調用載入同一個 AssetBundle,造成在卸載前重複載入也可能出現前面的錯誤訊息,所以,最好是能定義個陣列或其它變數來記錄載入中的 AssetBundle 有哪些,以此變數內容在載入 AssetBundle 前檢查。