Unity5.x AssetBundle 的变化

作者: 小飞不会飞_ | 来源:发表于2017-03-15 20:37 被阅读189次

    前言:下面文字适用于对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

    相关文章

      网友评论

        本文标题:Unity5.x AssetBundle 的变化

        本文链接:https://www.haomeiwen.com/subject/eladgttx.html