Unity5.x AssetBundle 的变化
前言:下面文字适用于对AssetBundle有一点了解的朋友,阅读大约10分钟,AssetBundle基本概念等知识可以网上找一下。
1、打包API的变化
Unity5.x中,AssetBundle相关的API做了极大的简化,合并了多个情况的函数,合并了资源和场景打包函数。
BuildPipeline API.png
1、资源
- unity4.x :
BuildAssetBundle,BuildAssetBundleExplicitAssetNames。当然不止两个函数,共有10个左右的重载函数,
public static bool BuildAssetBundle(UnityEngine.Object mainAsset, UnityEngine.Object[] assets, string pathName, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
mainAssets:指定mainAsset,这样解析该AB包的时候可以通过assetBundle.mainAsset得到
assets:指定打在一起的Asset,解析时可通过LoadAsset(“name”)得到
pathName:打包生成的存储路径
assetBundleOptions:打包选项
BuildTarget:打包目标平台
BuildAssetBundleExplicitAssetNames增加对AssetBundle命名,在4.x中,资源打包后的名字就是主资源的名字。
- unity5.x :
首先打包资源的设置方式有所变化,直接在编辑器中选中要打包的资源,在Inspector视图下就出现如下选项
AssetBundle设置
bundle名字可以在中Editor设置;也可以通过代码设置,需要用AssetBundleBuild包装起来。
简单说就是,只要给你要打包的资源设置一个AssetBundleName,在打包的时候Unity就是自动对这些设置了名字的资源进行打包,名字相同的打成一个bundle,并且自动处理资源间的依赖关系,最后生成每个AssetBundle文件和对应一个AssetBundleManifest文件,AssetBundleManifest中记录的就是该bundle的依赖等信息。
使用BuildPipeline.BuildAssetBundles对游戏资源打包:API地址
public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
public static AssetBundleManifest BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
outputPath : 生成到目录
AssetBundleBuild:对要打包的资源的封装的数组
该结构有三个属性,assetBundleName(设置AssetBundle的名字)、assetBundleVariant(可以理解为二级名字)、assetNames(数组)
Variant参数:
在Inspector界面最下方,除了可以设置AssetBundle的名字,后面还可以指定Variant参数。打包时,Variant会作为后缀添加在Bundle名字之后,相同的AssetBundle Name,不同的Variant Name,能够让AssetBundle方便地进行“多分辨率支持”。
Variant.png
BuildAssetBundleOptions : 打包选项(后面详细说)
BuildTarget : 打包的目标平台,Android or iOS ,and many
参数基本和unity4.x类似,只是资源的表示方式上不同。
2、场景
- unity4.x : BuildStreamedSceneAssetBundle
public static string BuildStreamedSceneAssetBundle(string[] levels, string locationPath, BuildTarget target, BuildOptions options);
- unity5.x:场景和资源共用一个打包函数,对普通资源的设置AssetBundle方式,同样可以对场景资源使用,也就是说,5.x中所有打包AssetBundle的资源都用一个接口了。
3、选项
BuildAssetBundleOptions:AssetBundle的打包策略,可以根据需求设置最合适的打包策略。
- None:使用默认打包方式(unity4.x中是LZMA压缩、不完备?不收集依赖?不生成唯一ID;unity5.x是LZMA压缩、资源完备、收集依赖、唯一ID)
- UncompressedAssetBundle:打包AssetBundle不进行压缩;
- CompleteAssets:用于保证资源的完备性(把该资源和它所有依赖打包到一个AssetBundle中),unity5.x默认开启;
- CollectDependencies:用于收集资源的依赖项(在打包的时候,会不会去找到依赖是否已经打成了AssetBundle,只把没有生成AssetBundle的依赖打进包内)unity5.x默认开启;
- DeterministicAssetBundle:为资源维护固定ID(唯一标志AssetBundle,主要用来增量打包),unity5.x默认开启;
Unity5.x新增
- ForceRebuildAssetBundle:用于强制重打所有AssetBundle文件;
- IgnoreTypeTreeChanges:判断AssetBundle更新时,是否忽略TypeTree的变化;
- DisableWriteTypeTree:不包含TypeTree类型信息(影响资源版本变化,可以让AssetBundle更小,加载更快;与4.x不同的是,对于移动平台,5.x下默认会将TypeTree信息写入AssetBundle);
- AppendHashToAssetBundleName:用于将Hash值添加在AssetBundle文件名之后,开启这个选项 可以直接通过文件名来判断哪些Bundle的内容进行了更新(4.x下普遍需要通过比较二进制等方法来判断,但在某些情况下即使内容不变重新打包,Bundle的二进制也会变化);
- ChunkBasedCompression:用于使用LZ4格式进行压缩,5.3新增,默认压缩格式为LZMA;
LZMA(Ziv-Markov chain algorithm)格式
Unity打包成AssetBundle时的默认格式,会将序列化数据压缩成LZMA流,使用时需要整体解包。优点是打包后体积小,缺点是解包时间长,且占用内存。
LZ4格式
5.3新版本添加的压缩格式,压缩率不及LZMA,但是不需要整体解压。LZ4是基于chunk的算法,加载对象时只有响应的chunk会被解压。
- StrictMode:任何错误都将导致打包失败;
2、打包过程
1、资源
unity4.x:下面代码是将某个文件中的材质打包,GetDependencies可以将直接和间接的依赖都找到。
public void BuildABundle()
{
string[] assets = AssetDatabase.FindAssets("t:mat", new string[] { "Assets/Material" });
string[] deps = AssetDatabase.GetDependencies(assets);
for (int i = 0; i < deps.Length; ++i)
{
Object ast = AssetDatabase.LoadAssetAtPath(deps[i], typeof(Object));
BuildPipeline.BuildAssetBundle(ast, new Object[] { }, "Assets/AssetBundles/Material"
, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
, BuildTarget.StandaloneWindows64);
}
}
unity5.x:
- 如果在Editor下设置了AssetBundle的Name,一句话就搞定
public void BuildABundle()
{
BuildPipeline.BuildAssetBundles(Application.dataPath + "/AssetBundles"
, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
}
- 也可以通过代码指定
public void BuildABundle()
{
List<AssetBundleBuild> wrap = new List<AssetBundleBuild>();
string[] assets = AssetDatabase.FindAssets("t:mat", new string[] { "Assets/Material" });
string[] deps = AssetDatabase.GetDependencies(assets);
string path = "";
AssetBundleBuild build;
for (int i = 0; i < deps.Length; ++i)
{
path = deps[i];
build = new AssetBundleBuild();
build.assetBundleName = this.GetFileName(path) + "_mat.assetBundle";
//build.assetBundleVariant
build.assetNames = new string[] { path };
wrap.Add(build);
}
BuildPipeline.BuildAssetBundles("", wrap.ToArray(), BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
}
public string GetFileName(string inputPath)
{
int index = inputPath.LastIndexOf('\\');
if (index < 0)
{
index = inputPath.LastIndexOf('/');
}
int start = index + 1;
start = (start < 0 ? 0 : start);
int end = inputPath.LastIndexOf('.');
end = (end < 0 ? inputPath.Length : end);
return inputPath.Substring(start, end - start);
}
需要注意:Unity会将AssetBundle名字设置为相同的资源打包在一个bundle里面。
2、场景
unity4.x:
string[] assets = AssetDatabase.FindAssets("t:unity", new string[] { "Assets/Scene" });
BuildPipeline.BuildStreamedSceneAssetBundle(assets, "Assets/AssetBundles/Scene", BuildTarget.StandaloneWindows64);
unity5.x:和打包普通资源一样。
以上的代码只是作为例子,实际打包时考虑的东西有很多。
3、依赖打包
unity4.x:不是一般的麻烦,需要自己去建立依赖关系,并且自己维护依赖数据。提供了两个API,分别是BuildPipeline.PushAssetDependencies和BuildPipeline.PopAssetDependencies。现在已经很少用,所以简单写一下
public void BuildABundle()
{
string[] assets = AssetDatabase.FindAssets("t:unity", new string[] { "Assets/Scene" });
Object ast = null;
string[] deps = null;
for (int i = 0; i < assets.Length; ++i)
{
deps = AssetDatabase.GetDependencies(new string[] { assets[i] });
BuildPipeline.PushAssetDependencies();
for (int j = 0; j < deps.Length; ++j)
{
if (assets[i] != deps[j])
{
ast = AssetDatabase.LoadAssetAtPath(deps[j], typeof(Object));
BuildPipeline.BuildAssetBundle(ast, new Object[] { }, "Assets/AssetBundles/Scene"
, BuildAssetBundleOptions.None //打包策略需要仔细考虑,这里随便写的一个
, BuildTarget.StandaloneWindows64);
}
}
BuildPipeline.PopAssetDependencies();
ast = AssetDatabase.LoadAssetAtPath(assets[i], typeof(Object));
BuildPipeline.BuildAssetBundle(ast, new Object[] { }, "Assets/AssetBundles/Scene"
, BuildAssetBundleOptions.None //打包策略需要仔细考虑,这里随便写的一个
, BuildTarget.StandaloneWindows64);
}
}
例子只是演示怎么用,真正写的时候会有一个递归的过程。
一个Push对应一个Pop,打包当前资源的时候,会把当前栈中的依赖做为依赖添加到资源bundle中。还得保证在打包某个资源之前,它的依赖已经打包好了,不然就会出现丢各种东西。
unity5.x:现在根本不用自己依赖打包,因为unity已经自动做了,所以代码和之前的一样。
4、依赖管理
unity4.x:自己把依赖关系记录下来,以便在加载的时候可以知道先加载的依赖,这样才能正确的加载资源,具体可查看这篇博客。
unity5.x:在打Bundle的时候,同时会为每个bundle生成一个配置文件(.manifest文件
),里面记录着bundle的信息
ManifestFileVersion: 0
CRC: 2829116721
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 2e559c427b01f4b1438782d05c4d59f2
TypeTreeHash:
serializedVersion: 2
Hash: 87623bb9f607f4edb72c2338fde167fc
HashAppended: 0
ClassTypes:
- Class: 1
Script: {instanceID: 0}
- Class: 4
Script: {instanceID: 0}
- Class: 21
Script: {instanceID: 0}
- Class: 23
Script: {instanceID: 0}
- Class: 33
Script: {instanceID: 0}
- Class: 43
Script: {instanceID: 0}
- Class: 65
Script: {instanceID: 0}
Assets:
- Assets/Cube.prefab
Dependencies:
- F:/Test/Assets/AssetBundles/mat.sd
版本号
CRC
Asset File的Hash Code,全局唯一ID
Type Tree的Hash Code,全局唯一ID,AssetBundle所包含的所有类型(目前不了解)
Class types:AssetBundle包含的所有"类型"(可以参看unity序列化)
Asset names:包含的资源
Dependent AssetBundle names:资源的所有依赖
在unity doc中详细的讲了里面有哪些信息,详情请点击这里
目前只是在做增量打包的时候会用到,但打包过程中完全不用管理依赖和manifest文件,因为无论是依赖还是增量,unity都已经做好了,在加载资源的时候只要调用统一的接口可以取到依赖,并且已经按照“正确的顺序”了,只需循环数组加载。
5、AssetBundle加载
unity4.x和unity5.x的资源加载方式并没有多大变化,API上没有变化,只是在依赖加载的时候,unity5.x实在太方便,既不需要自己做依赖关系,也不需要递归去加载。
先回顾一下API:
- 1、WWW加载
string netpath = "http://www.ab.com/down/test.assetBundle";//可以从网络上下载
string path = Application.persistentDataPath + "/ab";//也可以是本地路径
int versionCode = 1; //版本号
private IEnumerator _LoadAB()
{
WWW www = new WWW(path);
yield return www;
AssetBundle ab = www.assetBundle;
Object a = ab.LoadAsset("asset");
GameObject.Instantiate(a);
www.Dispose();
www = null;
ab.Unload(false);
}
private IEnumerator _LoadABorCache()
{
//先检查本地是否有缓存资源,没有再从网络获取,并且获取后进行缓存。
WWW www = WWW.LoadFromCacheOrDownload(netpath, versionCode);
yield return www;
AssetBundle ab = www.assetBundle;
Object a = ab.LoadAsset("asset");
GameObject.Instantiate(a);
www.Dispose();//释放掉WWW资源
www = null;
ab.Unload(false);
}
- 2、AssetBundle加载
unity4.x :
AssetBundle.CreateFromFile
AssetBundle.CreateFromMemory
AssetBundle.CreateFromMemoryImmediate
unity5.x :
AssetBundle.LoadFromFile
AssetBundle.LoadFromFileAsync
AssetBundle.LoadFromMemory
AssetBundle.LoadFromMemoryAsync
需要注意的是,unity5.x中,LoadFromFile和LoadFromFileAsync已经支持直接加载压缩文件了。并且新机制打包无法指定Assetbundle.mainAsset,因此无法再通过mainAsset来直接获取资源
开启DisableWriteTypeTree可能造成AssetBundle对Unity版本的兼容问题,但会使Bundle更小,同时也会略微提高加载速度。
- unity5.x加载代码
void Start () {
AssetBundle manifestAb = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundles");
AssetBundleManifest manifest = manifestAb.LoadAsset("AssetBundleManifest") as AssetBundleManifest;
string[] deps = manifest.GetAllDependencies("Cube");
List<AssetBundle> depList = new List<AssetBundle>();
for (int i = 0; i < deps.Length; ++i)
{
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + deps[i]);
depList.Add(ab);
}
AssetBundle cubeAb = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/Cube");
Object org = cubeAb.LoadAsset("Cube");
Instantiate(org);
cubeAb.Unload(false);
for (int i = 0; i < depList.Count; ++i)
{
depList[i].Unload(false);
}
manifestAb.Unload(true);
}
unity4.x : 会把文件整个生成内存镜像
AssetBundle加载以后,在内存中只是一块内存数据,没有特定的结构,内存包含了webstream、压缩数据(如果是压缩资源就包含)、解压buffer、解压后的数据。需要使用www.Dispose释放掉webstream的内存。
unity5.3+:最新的加载方式,只会载入header,在真正加载Asset的时候,通过这些header到文件中去取相应的数据。详情
5、资源加载
AssetBundle加载到内存中以后,还需要调用一些Load函数,把Asset加载出来,第一次Load的时候,会比较慢,unity会根据Asset的结构到原始数据中去找对应的内存,取到数据并且生成相应的结构,这样内存中就又多了一些Asset结构。
unity4.x :
AssetBundle.Load
AssetBundle.LoadAsync
AssetBundle.LoadAll
unity5.x :
AssetBundle.LoadAsset
AssetBundle.LoadAssetAsync
AssetBundle.LoadAllAssets
AssetBundle.LoadAllAssetsAsync
5、资源卸载
AssetBundle.Unload(flase)是释放AssetBundle文件的内存镜像,不包含Load创建的Asset内存对象。
AssetBundle.Unload(true)是释放那个AssetBundle文件内存镜像和并销毁所有通过该bundle创建的Asset内存对象。
有些没有引用的“游离”的资源,没有API直接卸载它的内存镜像,只有调用Resources.UnloadUnusedAssets,这个函数很耗时,它会遍历所有内存中的资源,找出并释放掉没有使用的资源。还可以用Resources.UnloadAsset(Object assetToUnload),卸载单个确定的“资源”,注意不是GameObject,因为很麻烦找某个GameObject所用的资源(Texture、Mesh、Anim、Audio),所以一般不会用它。