AssetBundle介绍

AssetBundle

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

AssetBundle里面有什么

可以归纳为两点:

  1. 它是一个存在硬盘的文件,可以称为压缩包。这个压缩包可以认为是一个文件夹。里面包含了多个文件。这些文件可以分为两类:
    serialized file 和resource file(序列化文件和源文件)。
  • serialized file :资源被打碎放在一个对象中,最后统一被写进一个单独的文件。(只有一个)
    prefab,模型 等
  • resource file 某些二进制资源(图片,声音)被单独保存,方便快速加载
  1. 它是一个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);

分组策略

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

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

  3. 按照使用分组
    把在某一时间内使用的所有资源打成一个包。可以按照关卡分,一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包。也可以按照场景分,一个场景所需要的资源一个包
    总结:
    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)

猜你喜欢

转载自blog.csdn.net/KindSuper_liu/article/details/122933778