在网络游戏中,可能需要在运行时下载并更新资源,而Unity提供了AssetBundle组件,可以将指定的一部分资源构建成AssetBundle文件。如果需要下载,那么需要将这些AssetBundle文件上传到CDN上。
设置AssetBundle
选择需要构建的AssetBundle资源,在右下角写入资源名以及资源的后缀名,在代码中调用BuildPipeline.BuildAssetBundles()
方法,只需提供一个输出的目录就可以构建AssetBundle了,最终将输出在AssetBundleFolder文件夹下


在Editor
文件夹内新建BuildAssetBundleScript
脚本,注意一定要在Editor文件夹中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| using UnityEngine; using UnityEditor; using System.IO;
public class BuildAssetBundleScript { [MenuItem("Tools/BuildAssetBundles")] static void BuildAssetbundles() { string outPath = Path.Combine(Application.dataPath, "StreamingAssets");
if (Directory.Exists(outPath)) Directory.Delete(outPath,true); Directory.CreateDirectory(outPath);
BuildPipeline.BuildAssetBundles(outPath, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows);
AssetDatabase.Refresh(); } }
|
执行BuildAssetBundles时一定要注意,如果场景中该放进Editor文件夹的脚本没有放在Editor文件夹里,就会全部报错
Unity - Scripting API: BuildPipeline.BuildAssetBundles (unity3d.com)

每个平台下的Bundle文件是不一样的,因此需要指定BuildTarget的构建平台。例如,在编辑模式下运行游戏,加载IOS或者Android的AssetBundle,尤其是Shader,都会显示错误,只能在真机上才能看到正确的效果
依赖关系
如果有两个Prefab都依赖了同一份材质和贴图文件,那么按照上面的打包方式,材质和贴图就会生成两份了。我们可以把它们依赖的材质和贴图也打成AssetBundle,这样两个Prefab就会自动依赖材质和贴图了。

分别打开两个Prefab的关系文件(.manifest),就能清楚地看到它们共同依赖同一个材质。

通过脚本设置依赖关系
我们可以通过AssetBundleBuild
,在脚本中指定打包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.IO;
public class BuildAssetBundleScript { [MenuItem("Tools/BuildAssetBundles")] static void BuildAssetbundles() { string outPath = Path.Combine(Application.dataPath, "StreamingAssets");
if (Directory.Exists(outPath)) Directory.Delete(outPath,true); Directory.CreateDirectory(outPath);
List<AssetBundleBuild> builds = new List<AssetBundleBuild>(); builds.Add(new AssetBundleBuild() { assetBundleName = "Cube.unity3d", assetNames = new string[] { "Assets/Resources/Cube.prefab", "Assets/Resources/Cube2.prefab" } }); builds.Add(new AssetBundleBuild() { assetBundleName = "cubesurf.unity3d", assetNames = new string[] { "Assets/Resources/Cubesurf.mat" } });
BuildPipeline.BuildAssetBundles(outPath, builds.ToArray(),BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows);
AssetDatabase.Refresh(); } }
|
压缩格式
AssetBundle 压缩 - Unity 手册 (unity3d.com)
AssetBundle提供了如下3种可选的压缩格式。
加载包体内的AssetBundle
本机使用 AssetBundle - Unity 手册 (unity3d.com)从手册中查看从网络或内存中读取AssetBundle的方式
包体内的AssetBundle只能放在StreamingAssets目录下,别的目录是无法读取的。可以使用AssetBundle.LoadFromFile()
或者AssetBundle.LoadFromFileAsync()
方法同步或者异步加载,加载AssetBundle之前,需要使用AssetBundleManifest提取每个AssetBundle的相互依赖关系。
新建LoadAssetBundleLocal
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| using UnityEngine; using System.IO;
public class LoadAssetBundleLocal : MonoBehaviour { void Start() { AssetBundle assetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "StreamingAssets")); AssetBundleManifest assetBundleManifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); foreach (var item in assetBundleManifest.GetAllDependencies("cube.unity3d")) { AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, item)); } assetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "cube.unity3d"));
GameObject prefab = assetBundle.LoadAsset<GameObject>("Cube"); GameObject.Instantiate<GameObject>(prefab); assetBundle.Unload(false); } }
|
使用Path.Combine(Application.streamingAssetsPath, "StreamingAssets")
可以帮助我们获得含有AssetBundleManifest
的AssetBundle,使用AssetBundleManifest.GetAllDependencies(assetbundlename)
来获得需要加载的AssetBundle的所有依赖AssetBundle,把这些依赖AssetBundle先全都加载在内存里,然后我们再创建目标AssetBundle时就不会有一些AssetBundle依赖丢失的问题。比如说这里如果不加载依赖的话,会生成一个紫色的没有贴图的小方块。
异步加载
新建LoadAssetBundleAsync
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| using System.Collections; using System.Collections.Generic; using UnityEngine; using System.IO;
public class LoadAssetBundleAsync : MonoBehaviour { private IEnumerator Start() { var assetBundleRequest = AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, "StreamingAssets")); yield return assetBundleRequest; var assetBundle = assetBundleRequest.assetBundle; var assetBundleManifestRequest = assetBundle.LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest"); yield return assetBundleManifestRequest; var assetBundleManifest = assetBundleManifestRequest.asset as AssetBundleManifest; foreach (var item in assetBundleManifest.GetAllDependencies("cube.unity3d")) { var dependencyBundleRequest = AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, item)); yield return dependencyBundleRequest; } var myAssetBundleRequest = AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, "cube.unity3d")); yield return myAssetBundleRequest; var myAssetBundle = myAssetBundleRequest.assetBundle; var myGameObjectRequest = myAssetBundle.LoadAssetAsync<GameObject>("Cube"); yield return myGameObjectRequest; var myGameObject = myGameObjectRequest.asset as GameObject; Instantiate<GameObject>(myGameObject); assetBundle.Unload(false); } }
|
Web下载AssetBundle
Unity提供了UnityWebRequestAssetBundle
来专用下载AssetBundle,此时只需要提供一个URL下载地址即可,如果下载本地任意资源,则在前面加上file://
即可。使用下面代码将其下载在Application.persistentDataPath
下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| using System.Collections; using System.Collections.Generic; using UnityEngine; using System.IO; using UnityEngine.Networking;
public class DownloadAssetBundle : MonoBehaviour { private IEnumerator Start() { string path = "file://" + Path.Combine(Application.streamingAssetsPath, "cube.unity3d");
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(path, 0); yield return request.SendWebRequest(); string name = Path.GetFileName(path); string filePath = Path.Combine(Application.persistentDataPath, name); if (File.Exists(filePath)) File.Delete(filePath);
WWW www = new WWW(path); File.WriteAllBytes(filePath, www.bytes); Debug.Log(filePath);
AssetBundleCreateRequest assetBundleRequest = AssetBundle.LoadFromFileAsync(filePath); yield return assetBundleRequest; AssetBundle myLoadedAssetBundle = assetBundleRequest.assetBundle; if (myLoadedAssetBundle == null) yield break;
GameObject myGameObject = myLoadedAssetBundle.LoadAsset<GameObject>("Cube");
if(myGameObject) Instantiate<GameObject>(myGameObject);
myLoadedAssetBundle.Unload(false); } }
|
加载场景
场景构建AssetBundle的方法和资源类似。而且从Bundle中加载场景不需要提前在Scenes In Build中添加场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using UnityEngine; using System.IO; using UnityEngine.SceneManagement;
public class LoadAssetBundleScene : MonoBehaviour { void Start() { AssetBundle assetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "testscene.unity3d")); }
private void OnGUI() { if(GUILayout.Button("<size=50>加载AB包场景</size>")) { SceneManager.LoadScene("SceneForAssetBundle"); } } }
|
卸载AssetBundle
关于AssetBundle的包管理,可以使用Addressables | Addressables | 1.19.19 (unity3d.com)Unity官方提供的包管理系统。
在同一个AssetBundle文件中,可以同时构建多个Assets。正确地从AssetBundle中实例化一个Prefab的步骤是:
- 从硬盘中加载AssetBundle;
- 从AssetBundle中加载需要的Asset;
- 从Asset中读取Object,将其Instantiate。
此时内存中有三个对象,AssetBundle对象、Asset对应的对象(比如GameObject,Texture)、Instantiate的对象
GameObject.Destroy()
只能卸载Instantiate的对象
Resources.UnloadUnusedAssets()
无法卸载Asset对应的对象,因为它被AssetBundle对象引用着
AssetBundle.UnloadAllAssetBundles(true)
方法可以卸载所有Asset及其引用的AssetBundle。推荐在确保AssetBundle和Asset都没用了(比如关卡结束或加载屏幕期间)调用。
AssetBundle.Unload(true)
。维护一个Asset的引用计数,当确保这个Asset被引用的所有GameObject都不再被使用时调用此函数
AssetBundle.Unload(false)
消除场景和代码中对不需要的对象的所有引用。完成后,调用Resources.UnloadUnusedAssets
来销毁残留的Asset。当以非附加的方式加载一个场景时,会自动销毁当前中的所有对象并自动调用Resources.UnloadUnusedAssets