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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 翻译:莫铭原文地址:AssetBundle usage patterns 本系列中的上一篇文章覆盖了AssetBu...
    莫铭阅读 5,220评论 1 12
  • 这部分主要讨论了AssetBundle的如下知识: AssetBundle的基础知识 使用AssetBundle的...
    Wenchao阅读 1,590评论 0 5
  • KSFramework是一个Unity 5 Asset Bundle开发框架和工具集,专注于运行时热重载,使用了S...
    陈凯利阅读 5,111评论 11 81
  • 这一章来说说AssetBundles,介绍下它的基础系统,还有一些和AssetBundles进行交互的核心API。...
    莫铭阅读 2,819评论 6 10
  • 布谷鸟和耕牛吵醒沉睡了一整个冬天的江南梯田,它懒懒地翻了两个身。秋萍的父亲将谷粒撒入梯田怀里,它听到了谷粒“蹬被子...
    砖儿zr阅读 1,181评论 10 3