Unity5.x AssetBundle打包详解

作者: Aodota | 来源:发表于2016-12-06 13:18 被阅读12973次

    Unity5.x AssetBundle打包详解

    在网上查看了很多资料,想详细搞清楚AssetBundle的原理。以实现符合项目需求的打包工具和加载逻辑

    1. AssetBundle是什么?

    AssetBundle是Unity用于动更的一种资源打包格式,如果某个资源需要动更的话,它必须被打包成AssetBundle

    2. AssetBundle打包常见要面临的问题?

    • 如何组织打包
    • 如何避免资源重复打包
    • 打包的资源如何加载

    3. 我的打包方案

    Unity5.x已经大幅度简化了AssetBundl的打包过程,理论上只需要调用一个API即可以完成打包

    BuildPipeline.BuildAssetBundles(RES_OUTPUT_PATH, BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.StandaloneOSXIntel); 
    

    但资源之间的依赖关系,避免资源重复打包的问题还是要自己解决。Unity5.x提供了一个很好的东西,就是可以给资源设置AssetBundleName


    Unity5.x中会将设置AssetBundleName相同的资源打包到一起,所以我们的打包过程其实就是把需要打包到一起的资源设置成相同的AssetBundleName,为每个资源设置AssetBundleName。最后调用打包API即可完成打包

    我目前使用的打包规则是:

    • prefab、scene等文件单独打包
    • 图片资源按文件夹打包

    关键代码如下:

        /// <summary>
        /// 设置AssetBundleName
        /// </summary>
        /// <param name="fullpath">Fullpath.</param>
        public static void setAssetBundleName(string fullPath) 
        {
            string[] files = System.IO.Directory.GetFiles (fullPath);
            if (files == null || files.Length == 0) {
                return;
            }
            Debug.Log ("Set AssetBundleName Start......");
            string dirBundleName = fullPath.Substring (RES_SRC_PATH.Length);
            dirBundleName = dirBundleName.Replace ("/", "@") + ASSET_BUNDLE_SUFFIX;
            foreach (string file in files) {
                if (file.EndsWith (".meta")) {
                    continue;
                }
                AssetImporter importer = AssetImporter.GetAtPath (file);
                if (importer != null) {
                    string ext = System.IO.Path.GetExtension (file);
                    string bundleName = dirBundleName;
                    if (null != ext && (ext.Equals (".prefab")||ext.Equals(".unity"))) {
                        // prefab单个文件打包
                        bundleName = file.Substring (RES_SRC_PATH.Length);
                        bundleName = bundleName.Replace ("/", "@");
                        if (null != ext) {
                            bundleName = bundleName.Replace (ext, ASSET_BUNDLE_SUFFIX);
                        } else {
                            bundleName += ASSET_BUNDLE_SUFFIX;
                        }
                    }
                    bundleName = bundleName.ToLower ();
                    Debug.LogFormat ("Set AssetName Succ, File:{0}, AssetName:{1}", file, bundleName);
                    importer.assetBundleName = bundleName;
                    EditorUtility.UnloadUnusedAssetsImmediate();
                    // 存储bundleInfo
                    AssetBuildBundleInfo info = new AssetBuildBundleInfo();
                    info.assetName = file;
                    info.fileName = file;
                    info.bundleName = bundleName;
                    if (null != ext) {
                        info.fileName = file.Substring (0, file.IndexOf (ext));
                    }
                    fileMap.Add (file, info);
                    List<AssetBuildBundleInfo> infoList = null;
                    bundleMap.TryGetValue(info.bundleName, out infoList);
                    if (null == infoList) {
                        infoList = new List<AssetBuildBundleInfo> ();
                        bundleMap.Add (info.bundleName, infoList);
                    }
                    infoList.Add (info);
                } else {
                    Debug.LogFormat ("Set AssetName Fail, File:{0}, Msg:Importer is null", file);
                }
            }
            Debug.Log ("Set AssetBundleName End......");
        }
    

    打包好之后生成依赖关系的配置文件,配置文件格式如下:

    <files>
        <file>
            <bundleName>prefabs@login.unity3d</bundleName>
            <fileName>Assets/Resources/Prefabs/Login</fileName>
            <assetName>Assets/Resources/Prefabs/Login.prefab</assetName>
            <deps>
                <dep>textures@common.unity3d</dep>
                <dep>textures@login.unity3d</dep>
            </deps>
        </file>
        <file>
            <bundleName>scenes@main.unity3d</bundleName>
            <fileName>Assets/Resources/Scenes/Main</fileName>
            <assetName>Assets/Resources/Scenes/Main.unity</assetName>
        </file>
        <file>
            <bundleName>textures@common.unity3d</bundleName>
            <fileName>Assets/Resources/Textures/Common/btn_blue</fileName>
            <assetName>Assets/Resources/Textures/Common/btn_blue.png</assetName>
        </file>
        <file>
            <bundleName>textures@common.unity3d</bundleName>
            <fileName>Assets/Resources/Textures/Common/btn_red</fileName>
            <assetName>Assets/Resources/Textures/Common/btn_red.png</assetName>
        </file>
        <file>
            <bundleName>textures@login.unity3d</bundleName>
            <fileName>Assets/Resources/Textures/Login/bg</fileName>
            <assetName>Assets/Resources/Textures/Login/bg.png</assetName>
        </file>
        <file>
            <bundleName>textures@login.unity3d</bundleName>
            <fileName>Assets/Resources/Textures/Login/text_input</fileName>
            <assetName>Assets/Resources/Textures/Login/text_input.png</assetName>
        </file>
    </files>
    
    • bundleName 打包的文件名
    • fileName 包里包含的文件名
    • assetName 包里的AssetName
    • deps 依赖的其他bundleName

    上述过程就完成了打包,具体可以参考的github: https://github.com/aodota/TestUnity

    相关文章

      网友评论

      • Leovany:大大,安卓下读取StreamingAssets失败了。。。
        查了下资源 说安卓下要用 www读取,大大有更好的优化意见吗
        Aodota:@々嗜血残阳 :+1:
        Leovany:@Aodota 找到问题了,LoadAsset方法下的System.IO.File.Exists ()判断,在安卓下是不能读取的,因为StramAsset 不是物理内存 存不到, 而persistentDataPath 可以取到
        Aodota:@々嗜血残阳 Android下面5.x之后可以使用AssetBundle.LoadFromFile读取
      • 超级苦瓜:请问 如果在做增量更新的时候,怎么判断一个角色预制体是在resouce目录下还是在assetbunli里呢 要在打包bundle的时候把资源记录下来吗
        Aodota:@6653e1a13fbe 不是的,这个是个文件搜索路径的问题。一般我的做法是优先搜索动更目录,如果动更目录文件不存在,在搜索原本目录
      • 晓龙酱:根目录的AssetBundleManifest已经记录所有AB的依赖关系了,可以直接使用。
        Aodota:@晓龙酱 是的,我自己生成xml配置文件,主要是为了能通过文件锁定到ab包,自己定制的方便自己使用哈
        Aodota:@晓龙酱 是的,我主要是记录了每个AB里面具体包含的资源文件,方便反向查找。这样代码里面就无需指定加载哪个AB了。
      • Ohno鹹魚:我想问一下~ 对资源设置AssetBundleName是一定要手动在inspection里设置么?(刚开始看assetBundle感到一阵眩晕...)
        Ohno鹹魚:@Aodota 嗯嗯~ 我刚刚看到了~ 然后有个地方,GetAllSubResDirs这个函数里面有句“else{dirList.Add (fullPath);}”,我觉得是不是不该放在else里?例如我在Textures文件夹下既有子目录也有文件,如果放在else里好像并不会将Textures目录下的文件打包,只会打包子目录下的文件……(就是Textures并不会被存放到resList里,但是子目录Common和Login会,应该是这样...有点担心是不是自己搞错了.. =-=)
        Aodota:@Ohno鹹魚 可以使用代码设置,我的分享里面就是用代码设置的啊
      • 肖马克_蛮牛:挺不错的文章!点赞支持。交流以下内容:
        1. 优化一下AssetLoader.LoadAsset方法,asset加载完毕之后要释放,否则下次无法加载;依赖的资源保存在静态换成类中,避免多个预制体有相同引用。
        2. Resources下图片不能打包图集,建议下把文件夹改名为ResourcesAB,先打包图集,再打包AB,然后再改名为Resources。
        肖马克_蛮牛:@Aodota OK,还有其他的注意事项可以再写一篇分享一下,或者干脆做个插件!支持!
        Aodota: @肖马克_蛮牛 这篇文章只是抛砖引玉。在实际的项目中我们对你提的2个问题都有优化处理。
        1. 对于assetloader加载玩的assetbudle我们没有立即释放,立即释放是会节省一部分内存,但也会破坏assetbudle和它生成的实例之间的依赖关系,内存管理相对变得更加复杂些。我们目前只需要记住已经加载的assetbudle就好,切换场景时统一调用assetbudle.unload(true)就好。更加简单,实际应用中也还不错。另外同一个assetbudle我们是不会加载两次的,第二次会直接返回。同时我们还优化了循环引用的处理。实际项目中有时候还是会遇到循环引用的情况。
        2. 我们实际工作中已经不使用Resource目录,因为要求所有资源都能动更。对于图集我们在打包脚本中进行检测,相同图集的资源会被设置成相同的assetbudleName, 即使分布在不同的文件夹。这样可以避免图集被多次打包的情况
      • 清晨放的诺言:你的Demo里不能打断点啊,大哥,您知道么,打了断点不能逐语句。F10直接跳出去,除非,下一步继续打上断点,这样导致加载不到图集,图集是空的。
        Aodota:@清晨放的诺言 文中提到的就是解决方案啊,就是将每个资源设置AssetBundleName就可以避免资源的重复打包。对于图集资源,将图集资源对应的文件设置相同的AssetBundleName就可以
        清晨放的诺言:@Aodota 大哥,您这个“”但资源之间的依赖关系,避免资源重复打包的问题还是要自己解决。Unity5.x提供了一个很好的东西,就是可以给资源设置AssetBundleName。“解决了吗啊?,可以把解决掉吗?
        Aodota:@清晨放的诺言 不能打断点?不理解什么意思。图集是Unity自动打的,打包脚本如果不希望图集被重复打包,需要将图集打包的文件设置成相同AssetBundleName
      • bde04638cca8:textures@common.unity3d' can't be loaded because another AssetBundle with the same files is already loaded.
        我加载一个资源的时候 我再去加载另一个 会报错这种怎么处理啊
        Aodota: @MomoPush 加载器要解决依赖加载,已经加载的直接返回就好
      • UnityPlane:unity版本5.5 ,代码报错
      • AndrewFan:图片按文件夹打包就不方便过滤没有引用到的资源了。我记得原则应该是以prefab为基础,对所有被prefab引用的资源进行整体打包,打包时预估资源大小,控制粒度;以文件名散列存放到小包裹,不至于因为一个资源文件的变化,导致重新打包时后续包裹全体发生变动;筛选出共享资源并集中打独立散列包,减少全局依赖,优化内存的加载。
        AndrewFan:@AndrewFan 这个链接脚本只用于打包,不用加载它,它只负责把资源搜罗到一个包里,负责产生依赖
        AndrewFan:@Aodota 我是说如果整个文件夹打包,其中某个图片被修改了,那整包都会改变,所以把这些图片资源散列到小包存放比较合适。就算修改,也只影响那个小包。如果有些资源是需要直接加载的话,可以写个很简单的资源链接脚本,就是一个object数组就可以,把需要的资源挂拖到上面就行。这个用代码使用起来更方便,也好管理
        Aodota:@AndrewFan 你这种思路也很好,游戏中还是有些资源不是prefab加载的,是散图使用的。不过就算以prefab引用为基准,prefab引用中的任何一个资源改变了对应的整个assertbundle都不要重新打包。以文件夹打包我感觉就是直观吧,我是从cocos转过来的,习惯这种方式了。后续使用中会持续改进:smile:
      • 6a7e49f16f00:这样一个prefab 如果要用到一张比较小的图,就要加载这个文件夹的包?
        Aodota: @AnchorZh lua文件建议单独处理,因为一般必须要将lua文件做加密或者jit编译。这个时候你可以自己定义lua打包模式
        0b0a0bd3b5a0:楼主的这个方案是 资源文件加下每个资源文件达成一个AB包,,,如果有个需求,根据 变体 ,将某个文件夹 下所有文件一起达成一个AB包(比如Lua文件夹下所有的lua脚本),,,这该是什么思路,,,,求教:blush:
        Aodota:@Liaoer 这个还是要看你自己资源组织的方式啦,很灵活的 :smile:

      本文标题:Unity5.x AssetBundle打包详解

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