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),所以一般不会用它。

6、AssetBundle内存结构

AssetBundle内存结构.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值