AssetBundle

AssetBundle 是一个压缩包文件,包含模型、贴图、预制体、声音、甚至整个场景,可以在游戏运行的时候被加载出来。AssetBundle自身保存着互相的依赖关系,例如 AssetBundle A 中的材质可以引用 AssetBundle B 中的纹理。为了减小压缩包大小,可以根据内置算法LZMA 或 LZ4来压缩 AssetBundle。 把一些可以下载内容放在AssetBundle里面,可以减少安装包的大小。

AssetBundle里面有什么

可以归纳为两点:

  • 它是一个存在硬盘的文件,可以称为压缩包。这个压缩包可以认为是一个文件夹。里面包含了多个文件。这些文件可以分为两类: serialized file 和resource file(序列化文件和源文件)。
  • serialized file :资源被打碎放在一个对象中,最后统一被写进一个单独的文件。(只有一个) prefab,模型 等
  • resource file 某些二进制资源(图片,声音)被单独保存,方便快速加载
  • 它是一个AssetBundle对象,我们可以通过代码从一个特定的压缩包加载出来的对象。这个对象包含了所有我们当初添加到这个压缩包里面的内容,我们可以把这个对象加载出来使用。
  • AssetBundle 布局

    总而言之,AssetBundle 由两部分组成:标头和数据段。 标头包含有关 AssetBundle 的信息,例如其标识符、压缩类型和清单。清单是一个以对象名称为关键字的查找表。每个条目都提供一个字节索引,指示在 AssetBundle 的数据段中可以找到给定对象的位置。在大多数平台上,这个查找表被实现为一个平衡的搜索树。具体来说,Windows 和 OSX 衍生平台(包括 iOS)采用红黑树。因此,构建清单所需的时间将随着AssetBundle 中资产数量的增加而线性增加。 数据段包含通过序列化 AssetBundle 中的资产生成的原始数据。如果将 LZMA 指定为压缩方案,则压缩所有序列化资产的完整字节数组。如果改为指定 LZ4,则单独压缩单独资产的字节。如果不使用压缩,则数据段将保留为原始字节流。

    打包AssetBundle

    1.指定资源的AssetBundle属性(xxxa/xxx)这里的xxxa会生成目录.但这种方法太过麻烦,实际项目中一般编写编辑器工具来完成这一步工作。

    2.构建AssetBundle包,在这里我创建一个文件夹叫做Editor,建立c#文件。

    using UnityEditor;
    using System.IO;
    public class CreateAssetBundles{
        [MenuItem("Assets/Build AssetBundles")]
        static void BuildAllAssetBundles()
            string dir = "AssetBundles";
            if (Directory.Exists(dir) == false)
                Directory.CreateDirectory(dir);
            //压缩方式 None 使用LZMA算法压缩,将会将包整体压缩,压缩的包更小,但是加载时间更长 UncompressedAssetBundle 不压缩  ChunkBasedCompression 使用LZ4压缩,这是一种基于块的压缩算法,加载时候可以只解压需要的块, 加载时间很快
            //BuildTarget 使用平台
            BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.UncompressedAssetBundle, BuildTarget.StandaloneWindows64);
    

    压缩方式BuildAssetBundleOptions

  • BuildAssetBundleOptions.None:使用LZMA算法压缩,压缩的包更小,但是加载时间更长。使用之前需要整体解压。一旦被解压,这个包会使用LZ4重新压缩。使用资源的时候不需要整体解压。在下载的时候可以使用LZMA算法,一旦它被下载了之后,它会使用LZ4算法保存到本地上。
  • BuildAssetBundleOptions.UncompressedAssetBundle:不压缩,包大,加载快
  • BuildAssetBundleOptions.ChunkBasedCompression:使用LZ4压缩,压缩率没有LZMA高,但是我们可以加载指定资源而不用解压全部。 注意:使用LZ4压缩,可以获得可以跟不压缩想媲美的加载速度,而且比不压缩文件要小。
  • 加载AB包的资源

    当一个或多个父 AssetBundle 的 UnityEngine.Objects 引用一个或多个其他 AssetBundle 的 UnityEngine.Objects 时,一个 AssetBundle 依赖于另一个 AssetBundle。加载该AB包时候必须先加载依赖。 假设材料 A依赖纹理 B 。材质 A 打包到 AssetBundle 1 中,纹理 B 打包到 AssetBundle 2 中。在这个用例中,AssetBundle 2 必须在从 AssetBundle 1 加载材料 A之前加载。

    AssetBundle 之间的依赖关系使用两个不同的 API 自动跟踪,具体取决于运行时环境。在 Unity 编辑器中,可以通过AssetDatabase API 查询 AssetBundle 依赖项。可以通过AssetImporter API 访问和更改 AssetBundle 分配和依赖项。在运行时,Unity 提供了一个可选的 API,用于通过基于 ScriptableObject 的AssetBundleManifest API 加载在 AssetBundle 构建期间生成的依赖信息。

    		private AssetBundleManifest manifest;
            private void Start(){
                AssetBundle ab = DeLoadFromFile("cubewall.unity3d");
                GameObject obj = ab.LoadAsset<GameObject>("CubeWall");
                Instantiate(obj, Vector3.zero, Quaternion.identity);
            public AssetBundle DeLoadFromFile(string AbName){
                if (manifest == null) InitManifest();
                var dependencies = manifest.GetAllDependencies(AbName);
                foreach (var dependencie in dependencies)
                    AssetBundle.LoadFromFile(Const.BuildDir + dependencie);
                return AssetBundle.LoadFromFile(Const.BuildDir + AbName);
            public void InitManifest(){
                AssetBundle configAb = AssetBundle.LoadFromFile(Const.BuildDir + "AssetBundles");
                manifest = configAb.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
    

    通常,应尽可能使用 AssetBundle.LoadFromFile。这个 API 在速度、磁盘使用和运行时内存使用方面是最有效的。 对于必须下载或修补 AssetBundle的项目,强烈建议使用 Unity 5.3 或更高版本的项目使用 UnityWebRequest,使用 Unity 5.2 或更低版本的项目使用 WWW.LoadFromCacheOrDownload 使用UnityWebRequest或WWW.LoadFromCacheOrDownload时,请确保下载器代码在加载 AssetBundle 后正确调用Dispose 。或者,C# 的using语句是确保WWW或UnityWebRequest被安全处理的最方便的方法。

    AssetBundle.LoadFromFile(异步)

    AssetBundle.LoadFromFile是一个高效的 API,用于从本地存储(例如硬盘或 SD 卡)加载未压缩或 LZ4 压缩的 AssetBundle。 在桌面独立、控制台和移动平台上,API 将仅加载 AssetBundle 的标头,并将剩余数据留在磁盘上。AssetBundle 的对象将在调用加载方法(例如AssetBundle.Load )或取消引用它们的 InstanceID 时按需加载。在这种情况下不会消耗多余的内存。在 Unity 编辑器中,API 会将整个 AssetBundle 加载到内存中,就像从磁盘读取字节并使用AssetBundle.LoadFromMemoryAsync 一样。如果在 Unity 编辑器中分析项目,此 API 可能会导致在 AssetBundle 加载期间出现内存峰值。这不应影响设备上的性能,并且应在采取补救措施之前在设备上重新测试这些峰值。

     //有依赖,要取得依赖 (GetAllDependencies)可以取得所有依赖
            AssetBundle.LoadFromFile("AssetBundles/share.unity3d");
            //得到ab  指定了后缀这里要填写   这里的路径填从Assets目录开始的
            AssetBundle ab = AssetBundle.LoadFromFile("AssetBundles/cube.unity3d");
            //这里的Cube是GameObject的名字
            GameObject obPrefab = ab.LoadAsset<GameObject>("Cube");
            Instantiate(obPrefab);
            //Object[] objs=ab.LoadAllAssets();
            //foreach(Object o in objs)
            //    Instantiate(o);
    

    AssetBundleDownloadHandler

    UnityWebRequest API 允许开发人员准确指定 Unity 应如何处理下载的数据,并允许开发人员消除不必要的内存使用。使用 UnityWebRequest 下载 AssetBundle 的最简单方法是调用UnityWebRequest.GetAssetBundle。 类DownloadHandlerAssetBundle使用工作线程,它将下载的数据流式传输到固定大小的缓冲区,然后根据下载处理程序的配置方式,将缓冲的数据假脱机到临时存储或 AssetBundle 缓存。所有这些操作都发生在本机代码中,消除了扩展托管堆的风险。此外,此下载处理程序不会保留所有下载字节的本机代码副本,从而进一步减少了下载 AssetBundle 的内存开销。 LZMA 压缩的 AssetBundle 将在下载期间解压缩并使用 LZ4 压缩进行缓存。通过设置Caching.CompressionEnabled可以更改此行为。 下载完成后,下载处理程序的assetBundle属性提供对下载的AssetBundle 的访问,就像在下载的AssetBundle上调用了AssetBundle.LoadFromFile 一样。 如果向 UnityWebRequest 对象提供了缓存信息,并且请求的 AssetBundle 已经存在于 Unity 的缓存中,则 AssetBundle 将立即可用,并且此 API 的操作与AssetBundle.LoadFromFile相同。 在 Unity 5.6 之前,UnityWebRequest 系统使用固定的工作线程池和内部作业系统来防止过多的并发下载。线程池的大小不可配置。在 Unity 5.6 中,这些保护措施已被删除以适应更现代的硬件,并允许更快地访问 HTTP 响应代码和标头。

            string uri = @"http://localhost/AssetBundles/cubewall.unity3d";
            UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri);
            yield return request.Send();
            //两种方式
            //AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
            AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
            //使用里面的资源
            GameObject wallPrefab = ab.LoadAsset<GameObject>("CubeWall");
            Instantiate(wallPrefab);
    

    逻辑实体分组 a,一个UI界面或者所有UI界面一个包(这个界面里面的贴图和布局信息一个包) b,一个角色或者所有角色一个包(这个角色里面的模型和动画一个包) c,所有的场景所共享的部分一个包(包括贴图和模型)

    按照类型分组 所有声音资源打成一个包,所有shader打成一个包,所有模型打成一个包,所有材质打成一个包

    按照使用分组 把在某一时间内使用的所有资源打成一个包。可以按照关卡分,一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包。也可以按照场景分,一个场景所需要的资源一个包 1,把经常更新的资源放在一个单独的包里面,跟不经常更新的包分离 2,把需要同时加载的资源放在一个包里面 3,可以把其他包共享的资源放在一个单独的包里面 4,把一些需要同时加载的小资源打包成一个包 5,如果对于一个同一个资源有两个版本,可以考虑通过后缀来区分 v1 v2 v3 unity3dv1 unity3dv2

    AssetBundle的卸载

    在内存敏感的环境中仔细控制加载对象的大小和数量至关重要。当从活动场景中移除对象时,Unity 不会自动卸载对象。AssetBundles 本身必须小心管理。由本地存储上的文件(在 Unity 缓存中或通过AssetBundle.LoadFromFile加载的文件)支持的 AssetBundle 具有最小的内存开销,很少消耗超过几十 KB。但是,如果存在大量 AssetBundle,这种开销仍然会成为问题。 卸载有两个方面 1. 减少内存使用 2. 有可能导致丢失 管理资产和 AssetBundle 时要了解的最重要的事情是调用AssetBundle.Unload时的行为差异, unloadAllLoadedObjects参数为 true 或 false 。 此 API 将卸载正在调用的 AssetBundle 的标头信息。unloadAllLoadedObjects参数确定是否也卸载从此 AssetBundle 实例化的所有对象。如果设置为true ,那么来自 AssetBundle 的所有对象也将立即卸载——即使它们当前正在活动场景中使用。 例如,假设材质M是从 AssetBundle AB加载的,并假设M当前处于活动场景中。 如果调用 AssetBundle.Unload(true) ,则M将从场景中移除、销毁并卸载。但是,如果调用 AssetBundle.Unload(false) ,则AB的标头信息将被卸载,但M将保留在场景中并且仍然有效。调用AssetBundle.Unload(false)会破坏M和AB之间的链接。如果稍后再次加载AB ,则AB中包含的 Objects 的新副本将加载到内存中。 如果稍后再次加载AB ,则将重新加载 AssetBundle 头信息的新副本。但是,M没有从AB的这个新副本中加载。Unity 不会在AB和M的新副本之间建立任何链接。

    如果调用 AssetBundle.LoadAsset()来重新加载M ,Unity 不会将M的旧副本解释为AB中数据的实例。因此,Unity 将加载M的一个新副本,并且场景中将有两个相同的M副本。

    对于大多数项目,这种行为是不可取的。大多数项目应该使用AssetBundle.Unload(true)并采用一种方法来确保 Objects 不重复。两种常见的方法是:

  • 在应用程序的生命周期中有明确定义的点,在该点卸载瞬态 AssetBundle,例如在关卡之间或在加载屏幕期间。这是最简单和最常见的选择。
  • 维护单个对象的引用计数并仅在其所有组成对象均未使用时卸载 AssetBundle。这允许应用程序在不复制内存的情况下卸载和重新加载单个对象。
  • 如果应用程序必须使用AssetBundle.Unload(false) ,则只能通过两种方式卸载单个对象:

  • 消除场景和代码中对不需要的对象的所有引用。完成后,调用Resources.UnloadUnusedAssets
  • 以非附加方式加载场景。这将销毁当前场景中的所有对象并自动调用Resources.UnloadUnusedAssets。
  • 如果项目有明确定义的点,可以让用户等待对象加载和卸载,例如在游戏模式或关卡之间,这些点应该用于根据需要卸载尽可能多的对象并加载新的对象。 最简单的方法是将项目的离散块打包到场景中,然后将这些场景连同它们的所有依赖项一起构建到 AssetBundles 中。然后应用程序可以进入“加载”场景,完全卸载包含旧场景的 AssetBundle,然后加载包含新场景的 AssetBundle。 虽然这是最简单的流程,但有些项目需要更复杂的 AssetBundle 管理。由于每个项目都不同,因此没有通用的 AssetBundle 设计模式。 在决定如何将对象分组到 AssetBundle 中时,如果必须同时加载或更新它们,通常最好先将它们捆绑到 AssetBundle 中。例如,考虑一个角色扮演游戏。单个地图和过场动画可以按场景分组到 AssetBundle 中,但在大多数场景中都需要一些对象。可以构建 AssetBundle 以提供肖像、游戏内 UI 以及不同的角色模型和纹理。然后可以将后面的这些对象和资产分组到第二组 AssetBundle 中,这些 AssetBundle 在启动时加载并在应用程序的生命周期内保持加载状态。 如果在卸载 AssetBundle 后 Unity 必须从其 AssetBundle 重新加载一个对象,则会出现另一个问题。在这种情况下,重新加载将失败,并且该对象将作为(丢失的)对象出现在 Unity 编辑器的层次结构中。 这主要发生在 Unity 失去并重新获得对其图形上下文的控制时,例如当移动应用程序暂停或用户锁定他们的 PC 时。在这种情况下,Unity 必须将纹理和着色器重新上传到 GPU。如果这些资产的源 AssetBundle 不可用,应用程序会将场景中的对象呈现为洋红色。

                //没有引用的Asset都会从内存中卸载掉
                if (Input.GetKeyUp(KeyCode.Alpha0))
                    Resources.UnloadUnusedAssets();
                if (Input.GetKeyUp(KeyCode.Alpha1))
                    //卸载一个(非GameObject)Asset,如果该Asset仍被引用着(例如Material被GameObject引用着),Unity会重新从加载它,不会影响显示的GameObject
                    //如果没有被引用,Asset将会被卸载(从Memory-Detailed-Take Samele Editor中能看到)
                    //UnloadAsset may only be used on individual assets and can not be used on GameObject's / Components or AssetBundles
                    Resources.UnloadAsset(loadAsset.First().Value);
                if (Input.GetKeyUp(KeyCode.Alpha2))
                    // 如果参数unloadAllLoadedObjects传入false
                    // 此AssetBundle变为null,无法再从此AssetBundle中加载任何Object
                    // 已经从此AssetBundle中加载的Object仍能正常工作
                    loadBundle.First().Value.Unload(false);
                if (Input.GetKeyUp(KeyCode.Alpha3))
                    // 如果参数unloadAllLoadedObjects传入true
                    // 此AssetBundle变为null,无法再从此AssetBundle中加载任何Object
                    // 所有从此AssetBundle中加载的Object都将被销毁 
                    // Scene中对这些Object的引用将会丢失
                    loadBundle.First().Value.Unload(true);
    

    在 AssetBundle 构建过程中将生成 32 位校验和。当您通过 AssetBundle 加载 API 提供此 CRC 时,加载系统会在加载之前计算 AssetBundle 的校验和。如果 AssetBundle 的 CRC 与提供的 CRC 不匹配,则不会加载 AssetBundle。检查 CRC 可确保 AssetBundle 数据在构建后未被损坏或篡改。

    当AssetBundle需要从服务器上更新的时候,如果使用 WWW.LoadFromCacheOrDownload 或 UnityWebRequest 来管理应用程序的缓存 AssetBundle,则将不同的版本参数传递给所选 API 将触发新 AssetBundle 的下载。

    在修补系统中要解决的更难的问题是检测要替换的 AssetBundle。修补系统需要两个信息列表:

  • 当前已下载的 AssetBundle 及其版本控制信息的列表
  • 服务器上的 AssetBundle 及其版本控制信息的列表 修补程序应下载服务器端 AssetBundle 列表并比较这些 AssetBundle 列表。应重新下载缺少的 AssetBundle 或已更改版本控制信息的 AssetBundle。
  • 也可以编写一个自定义系统来检测 AssetBundle 的更改。自己编写系统的大多数开发人员会选择对 AssetBundle 文件列表使用行业标准数据格式(例如 JSON)和并使用标准 C# 类(例如 MD5)来计算校验和。

    Unity-Manual:AssetBundles

    Assets, Resources and AssetBundles

    AssetBundle(创建打包)入门学习(基于Unity2017)

    分类:
    Android
    标签: