AssetBundle简介
当我们提到AssetBundle时,实际上有两层含义。
- AssetBundle打包的资源,存在于我们的硬盘上或者远端的服务器上。此时,可以认为AssetBundle特指被压缩过的文件,包含Model、Texture等。在运行时,我们可以动态的加载这些对象到我们的场景当中。
- AssetBundle对象。这是对AssetBundle打包的资源的抽象,作为游戏访问资源的接口。实际上,我们要借助该对象先把资源从硬盘或者远端加载到内存中之后才能在运行时动态的加载。
AssetBundle的使用流程
- 为将要被打包的资源设置AssetBundle名字
- 打包AssetBundle资源到本地
- 上传AssetBundle资源到服务器(如果只是在本地加载那么这一步可以省略掉)
- 在运行时动态加载AssetBundle打包的资源
1.为将要被打包的资源设置AssetBundle名字
我们给资源设置AssetBundle名字是为了让Unity对其分门别类的打包。
新建一个场景,创建一个Cube和一个Spere,分别命名为CubeWall和SpereWall,随便给他们拖拽一个材质球。这里我的材质球的名字为Box,它包含两种贴图和一个shader。
例如:
以上这些都是我们正常的创建对象的过程,现在把CubeWall和Sphere拖到Project窗口中做成预制体。
点击,看Inspector面板:
最下方有AssetBundle,点击None可以new一个AssetBundle的name,这里只能小写,我设定的名字是:
这样会生成一个prefab的目录,可以通过这种方式把相关的东西放在一个目录下。例如音效可以放在audio目录下之后构建。
在后边还有一个为none的属性,这个设定什么都无所谓,但是一旦设定了,那么AssetBundle的那么就是它的全名。例如设定后缀为unity3d,那么该AssetBundle的对象就是prefab/cuabwall.unity3d.
这样AssetBundle的名字就设定好了。
2.打包AssetBundle资源到本地
这一步很简单,Unity提供了接口,我们只需要:
public class BuildAssetBundle{
[MenuItem("AssetBundle/Build All AssetBundle")]
static void BuildAllAssetBundle() {
string path = "AssetBundles";//相对目录,和Asset同级
if (!Directory.Exists(path)) {
Directory.CreateDirectory(path);
}
BuildPipeline.BuildAssetBundles(path,BuildAssetBundleOptions.None,BuildTarget.StandaloneWindows64);
//None使用LZMA算法压缩,被压缩的包相比LZ4更多,但是解压时间更久,加载时间更久,解压是必须整体解压
//ChunkBasedCompressor LZ4压缩 可以指定加载具体的资源而无需全部解压
}
}
这样点击Build All AssetBundle后会在Asset的同级目录AssetBundle下生成如下文件:
名为AssetBundle的文件是和我们创建的目录的名字同名的,我们无法打开它,AssetBundle.manifest是我们需要关心的。
AssetBundle.manifest实际上是一个文本文件,可以用记事本打开:
可以认为是一种类似json的格式化的文本描述文件,它包含了所有的被打包的资源的名字和依赖关系,由图可以看出这个项目打包了两个资源,分别为prefab/spherewall.unity3d和prefab/cubewall
这是spherewall.unity3d对应的manifest文件,可以看出它有一个Asset属性,指明了这个资源在项目中的位置。
现在有了AssetBundle,我们就可以将其上传到服务器当中,在运行时必要的地方从服务器获取资源并实例化它。
实际上,AssetBundle这种机制可以方便减少发布的游戏的体积,可以把一些经常更新的资源打包放到服务器上,一些运行必要的资源发布到apk中,这样玩家下载的apk的体积会很小,但是不影响运行。当运行到某个关卡时,再获取资源。
3.上传AssetBundle资源到服务器
其实这一步非常简单,只需要将AssetBundle打包生成的文件夹放到服务器的指定位置即可,这里不做演示。
4.在运行时动态加载AssetBundle打包的资源
现在有了AssetBundle打包的资源,我们就可以在运行时加载AssetBundle对象了。
加载AssetBundle主要有两种方式,本地加载和远程加载。
本地加载就是AssetBundle资源在本地,那么可以用如下代码加载:
void AssetLoadFromFile() {//也有异步的方法
//相对路径
AssetBundle ab = AssetBundle.LoadFromFile(cubePath);
GameObject cubeWall = ab.LoadAsset<GameObject>("CubeWall");//获取prefab
Instantiate(cubeWall,Vector3.zero,Quaternion.identity);//实例化prefab
}
本地加载很简单,只需要LoadFromFile获取AssetBundle对象后再通过AssetBundle对象使用LoadAsset方法即可,有对应的异步的版本,这个查API即可。
远程加载在5.3之前都是用的WWW类,但是官方已经不推荐这么做了,在5.3之后包括Unity2017,都推荐使用UnityWebRequest来完成。
IEnumerator WebRequest() {
string cubeURL = @"http://localhost/AssetBundles/prefab/cubewall.unity3d";
UnityWebRequest request = UnityWebRequest.GetAssetBundle(shareURL);
yield return request.Send();//发送http请求
if(string.IsNullOrEmpty(request.error){//没有错误
AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
GameObject cubeWall = ab.LoadAsset<GameObject>("CubeWall");//获取prefab
Instantiate(cubeWall,Quaternion.identity);//实例化prefab
}
else{
yield break;
}
}
之后,在需要加载资源的地方StartCoroutine(WebRequest())即可。
还有使用的LoadFromMemory方法,该方法是直接从给定的byte[]中加载数据,如果网络传输使用的是TCP协议获得了一个二进制流文件,那么可以使用这种方式加载AssetBundle资源,详细使用可以查API。
关于AssetBundle的依赖
我们已经走完了一个AssetBundle的工作流程,目前来说它运行的不错,可是有许多问题没有暴露出来。
在上述CubeWall和SphereWall这两个预制体中,我引用了同一个名为Box的材质球,这个材质球上有两张贴图和一个shader文件。
CubeWall和SpereWall是依赖于这些资源的,这些资源都被打包到了AssetBundle文件中。
其实这样是很浪费空间的,因为他们都含有一份Box的资源的拷贝。
所以,最好的办法是,我们将CubeWall和Sphere公共引用的部分单独打包。
在打包前,他们占用空间的信息如下:
我把Box单独打包为一个名为share的AssetBundle文件,接下来我们再看看他们的体积有何变化:
可以很明显的看出,虽然多了一个share的68k,但是cubewall和spherewall的体积明显减小了,这对我们显然是非常有利的。
接下来我们再看看cubewall.manifest文件有什么变化:
这里多出一个Dependencies属性,给出了它依赖的资源的AssetBundle的存放位置。
实际上,在AssetBundle.manifest中就存放了各自的依赖关系:
实际上,我们总结了两个常用的经验
/* * 1.经常更新的资源单独打包,和不经常更新的资源分开 * 2.被公共引用的资源单独打包 */
到这里一切都不错,但是如果你还使用之前的加载资源的方法,你会发现,加载出来的cubewall和sphere是紫红色的,这是因为你没有加载share这个AssetBundle,那么它就找不到Box这个材质球以及对应的贴图,所以最终就呈现了这种样貌。
所以我们必须压迫加载share这个AssetBundle到内存中才可以,所以你的加载代码变成了这样:
void AssetLoadFromFile() {//也有异步的方法
//相对路径
AssetBundle share = AssetBundle.LoadFromFile(sharePath);
//加载是被加载到内存当中
AssetBundle ab = AssetBundle.LoadFromFile(cubePath);
//加载assetbundle,如果没有加载它的依赖是不能正确显示cubeWall的,不存在加载的先后顺序,在LoadAsset之前即可
GameObject cubeWall = ab.LoadAsset<GameObject>("CubeWall");//获取prefab
Instantiate(cubeWall,Quaternion.identity);//实例化prefab
}
那么问题来了,你如何知道名为name的AssetBundle都依赖哪些东西呢?
还及得manifest文件吗,它保存了物体所有的依赖信息,而且Unity提供给我们读取manifest文件的,详细的使用方法可以查API,这里给出一个简单的实例:
void LoadWithManifest() {
//主manifest文件,从这个文件中可以获取本项目所有的AssetBundle的信息,这些信息存储在manifest里边
AssetBundle manifestAssetBundle = AssetBundle.LoadFromFile("AssetBundles/AssetBundles");
AssetBundleManifest manifest = manifestAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//得到了本项目中所有AssetBundle的名字
//string[] assetBundleNames = manifest.GetAllAssetBundles();
//想加载某个AssetBundle,加载它所有的依赖AssetBundle,这些依赖关系存在于主manifest里边
//加载所有依赖
string[] dependencies = manifest.GetAllDependencies("prefab/cubewall.unity3d");
foreach(var name in dependencies) {
AssetBundle.LoadFromFile("AssetBundles/" + name);
}
//加载所有依赖
AssetBundle ab = AssetBundle.LoadFromFile(cubePath);
Instantiate(ab.LoadAsset<GameObject>("CubeWall"),Quaternion.identity);
}
附录 : AssetBundle Browser
AssetBundle Browser是Unity官方推出的一个非常方便查看项目的AssetBundle的依赖关系的一个插件。
在github上直接搜索该项目即可,非常人性化:
在Build里可以构建AssetBundle文件,省去了我们自己写代码的麻烦(虽然并不长233)。
你可以看到,旁边有个三角号,提示我们这样会重复引用资源,甚至被重复引用的资源都给标出来了,所以是很方便的。
这里出现这个问题的原因是我们只把材质球打包,而材质球包含的贴图和shader文件并没有指定AssetBundle,被auto打包进资源了,建议把贴图和shader也一并打包,指定其AssetBundle为share即可,重新构建:
简易模板
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
public class LoadFromFile : MonoBehavIoUr {
/* * 推荐的方式 * 1.经常需要更新的资源打包 * 2.在经常需要更新的资源中,被共同依赖的单独打包到share当中 share/material share/audioclip share/lua 的目录结构 * 3.使用UnityWebRequest的方式从远端获取,使用AssetBundleManifest增加可控性 * 4.如果share的文件很多,而我只想获得x的依赖,那么使用GetAllDependence的方式先加载 */
private AssetBundleManifest MainManifest = null;
void Start () {
StartCoroutine(GetMainManifest());//获取MainManifest
StartCoroutine(LoadAsset("prefab/cubewall.unity3d","CubeWall"));
}
IEnumerator GetMainManifest() {
string url = @"http://localhost/AssetBundles/AssetBundles";
UnityWebRequest request = UnityWebRequest.GetAssetBundle(url);
yield return request.Send();
if (string.IsNullOrEmpty(request.error)) {
AssetBundle assetBundle = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
MainManifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
else {
//LogError()
yield break;
}
}
IEnumerator LoadDependencies(string assetName) {
string[] dependencies = MainManifest.GetAllDependencies(assetName);
foreach (var name in dependencies) {
string url = "http://localhost/AssetBundles/" + name;
UnityWebRequest request = UnityWebRequest.GetAssetBundle(url);
yield return request.Send();
if (string.IsNullOrEmpty(request.error)) {
AssetBundle assetBundle = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
}
else {
//LogError()
yield break;
}
}
}
IEnumerator LoadAsset(string assetName,string objName) {
while (MainManifest == null)
yield return null;
StartCoroutine(LoadDependencies(assetName));
string url = "http://localhost/AssetBundles/" + assetName;
UnityWebRequest request = UnityWebRequest.GetAssetBundle(url);
yield return request.Send();//发送http请求
if (string.IsNullOrEmpty(request.error)) {
AssetBundle assetBundle = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
GameObject go = ab.LoadAsset<GameObject>(objName);
Instantiate(go,Quaternion.identity);//实例化prefab
}
else {
//LogError()
yield break;
}
}
/* //Unity5.3及以上推荐的方式 IEnumerator WebRequest() { string shareURL = @"http://localhost/AssetBundles/share.unity3d"; string cubeURL = @"http://localhost/AssetBundles/prefab/cubewall.unity3d"; UnityWebRequest request = UnityWebRequest.GetAssetBundle(shareURL); yield return request.Send();//发送http请求 AssetBundle share = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle; request = UnityWebRequest.GetAssetBundle(cubeURL); yield return request.Send();//发送http请求 AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle; GameObject cubeWall = ab.LoadAsset<GameObject>("CubeWall");//获取prefab Instantiate(cubeWall,Quaternion.identity);//实例化prefab } void LoadWithManifest() { //主manifest文件,这些信息存储在manifest里边 AssetBundle manifestAssetBundle = AssetBundle.LoadFromFile("AssetBundles/AssetBundles"); AssetBundleManifest manifest = manifestAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); //得到了本项目中所有AssetBundle的名字 //string[] assetBundleNames = manifest.GetAllAssetBundles(); //想加载某个AssetBundle,这些依赖关系存在于主manifest里边 //加载所有依赖 string[] dependencies = manifest.GetAllDependencies("prefab/cubewall.unity3d"); foreach(var name in dependencies) { AssetBundle.LoadFromFile("AssetBundles/" + name); } //加载所有依赖 AssetBundle ab = AssetBundle.LoadFromFile(cubePath); Instantiate(ab.LoadAsset<GameObject>("CubeWall"),Quaternion.identity); } */
}