描述

一个存档文件,包含可在运行时加载的特定于平台的资源(模型、纹理、预制件、音频剪辑甚至整个场景)

其可表达彼此之间的依赖关系:例如 AssetBundle A中的材质可以引用 AssetBundle B中的纹理

为了通过网络进行有效传递,可以根据用例要求选用内置算法来压缩 AssetBundle(LZMA 和 LZ4)

优点

AssetBundle 可用于可下载内容(DLC),减小初始安装大小,加载针对最终用户平台优化的资源,以及减轻运行时内存压力

包含

磁盘上的实际文件:称之为 AssetBundle存档,存档可以被视为一个容器,就像文件夹一样,可以在其中包含其他文件。这些附加文件包含两种类型:

  • 序列化文件:包含分解为各个对象并写入此单个文件的资源
  • 资源文件:只是为某些资源(纹理和音频)单独存储的二进制数据块,允许我们有效地在另一个线程上从磁盘加载它们
  • 通过代码进行交互以便从特定存档加载资源的实际 AssetBundle 对象。此对象包含一个映射,即从已添加到此存档的资源的所有文件路径到按需加载的资源所包含的对象之间的映射

    使用流程

    指定资源的AssetBundle属性

    AssetBundle_01

    构建AssetBundle包

    上传AssetBundle包

    加载AssetBundle包和包里面的资源

    分组策略

    根据需要随意混合和搭配以下策略:

    逻辑实体分组

    根据资源所代表的项目功能部分将资源分配给 AssetBundle。这包括各种不同部分,比如用户界面、角色、环境以及在应用程序整个生命周期中可能经常出现的任何其他内容

    非常适合于可下载内容 (DLC),因为通过这种方式将所有内容隔离后,可以对单个实体进行更改,而无需下载其他未更改的资源,但是开发人员必须熟悉项目使用每个资源的准确时机和场合

  • 一个UI界面或者所有UI界面一个包(界面所有贴图和布局数据)
  • 一个角色或者所有角色一个包(这个角色里面的模型和动画一个包)
  • 所有的场景所共享的部分一个包(包括贴图和模型)
  • 类型分组

    将相似类型的资源(例如音频轨道或语言本地化文件)分配到单个 AssetBundle

    要构建供多个平台使用的 AssetBundle,类型分组是最佳策略之一

  • 所有声音资源一个包
  • 所有shader一个包
  • 并发内容分组

    把在某一时间内使用的所有资源打成一个包

  • 按关卡分,一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包
  • 按场景分,一个场景所需要的资源一个包
  • 把经常更新的资源放在一个单独的包里面,跟不经常更新的包分离
  • 把需要同时加载的资源放在一个包里面
  • 把其他包共享的资源放在一个单独的包里面
  • 把一些需要同时加载的小资源打包成一个包
  • 对于一个同一个资源有两个版本,可考虑通过后缀来区分 v1 v2 v3
  • 构建

    描述

    在 Assets 文件夹中创建一个名为 Editor 的文件夹,并将包含以下内容的脚本放在该文件夹中。

    此脚本将在 Assets 菜单底部创建一个名为“Build AssetBundles”的菜单项,该菜单项将执行与该标签关联的函数中的代码。单击 Build AssetBundles 时,将随构建对话框一起显示一个进度条。此过程将会获取带有 AssetBundle 名称标签的所有资源,并将它们放在 abPath 定义的路径中的文件夹中

    BuildAssetBundles语法

    1
    BuildPipeline.BuildAssetBundles(Path, BuildAssetBundleOptions, BuildTarget);
    Path:指定AssetBundles资源存放的路径
    BuildAssetBundleOptions:压缩方式

    常用的三个:

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

    使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    using System.IO;
    using UnityEditor;

    public class CreateAssetBundles
    {
    [MenuItem("Assets/Build AssetBundles")]
    static void BuildAllAssetBundles()
    {
    string abPath = "Assets/AssetBundles";
    if (!Directory.Exists(abPath))
    {
    Directory.CreateDirectory(abPath);
    }
    BuildPipeline.BuildAssetBundles(abPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
    }
    }

    使用AssetBundle

    可以使用四种不同的 API 来加载 AssetBundle。它们的行为根据加载捆绑包的平台和构建 AssetBundle 时使用的压缩方法(未压缩、LZMA 和 LZ4)而有所不同

    AssetBundle.LoadFromMemoryAsync

    从内存中加载

  • 若包采用的是 LZMA 压缩方式,将在加载时解压缩 AssetBundle
  • 若包采用的是LZ4 压缩方式,则会以压缩状态加载
  • 语法

    1
    2
    3
    4
    //binary: AssetBundle数据的字节数组
    //crc: crc校验码(选填)
    AssetBundleCreateRequest LoadFromMemoryAsync(byte[] binary, uint crc);//异步
    AssetBundle LoadFromMemory(byte[] binary, uint crc);//同步

    使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using UnityEngine;

    public class LoadFromFile : MonoBehaviour
    {
    private string path = "Assets/AssetBundles/scene/cubewall";//预制体的路径
    private void Start()
    {
    //StartCoroutine("LoadFromMemoryAsync");
    //LoadFromMemory();
    }
    #region LoadFromMemory方式
    //异步的方式
    IEnumerator LoadFromMemoryAsync()
    {
    AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
    yield return request;
    AssetBundle ab = request.assetBundle;
    if (ab == null)
    {
    Debug.Log("加载失败");
    }
    else
    {
    GameObject obj = ab.LoadAsset<GameObject>("Cube");
    Instantiate(obj);
    }
    }
    //同步的方式
    private void LoadFromMemory()
    {
    AssetBundle ab = AssetBundle.LoadFromMemory(File.ReadAllBytes(path));
    if (ab == null)
    {
    Debug.Log("加载失败");
    }
    else
    {
    GameObject obj = ab.LoadAsset<GameObject>("Cube");
    Instantiate(obj);
    }
    }
    #endregion
    }

    AssetBundle.LoadFromFile

    从本地存储中加载未压缩的捆绑包时,此 API 非常高效。

  • 若包未压缩或采用了数据块 (LZ4) 压缩方式,LoadFromFile 将直接从磁盘加载捆绑包
  • 若包完全压缩 (LZMA),首先解压缩捆绑包,然后再将其加载到内存中
  • 语法

    1
    2
    3
    4
    5
    //path: 路径
    //crc: crc校验码(选填)
    //offset: 字节数组偏移量(选填)
    AssetBundleCreateRequest LoadFromFileAsync(string path, uint crc, ulong offset);//异步
    AssetBundle LoadFromFile (string path, uint crc, ulong offset);//同步

    使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using UnityEngine;

    public class LoadFromFile : MonoBehaviour
    {
    private string path = "Assets/AssetBundles/scene/cubewall";//预制体的路径
    private void Start()
    {
    //LoadFromFileFunc();
    //StartCoroutine("LoadFromFileAsync");
    }
    #region LoadFromFile方式
    //同步的方式
    private void LoadFromFileFunc()
    {
    AssetBundle ab = AssetBundle.LoadFromFile(path);
    if (ab == null)
    {
    Debug.Log("加载失败");
    }
    else
    {
    GameObject obj = ab.LoadAsset<GameObject>("Cube");
    Instantiate(obj);
    }
    }
    //异步的方式
    IEnumerator LoadFromFileAsync()
    {
    AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);
    yield return request;
    AssetBundle ab = request.assetBundle;
    if (ab == null)
    {
    Debug.Log("加载失败");
    }
    else
    {
    GameObject obj = ab.LoadAsset<GameObject>("Cube");
    Instantiate(obj);
    }
    }
    #endregion
    }

    WWW.LoadFromCacheOrDownload

    A从远程服务器下载 AssetBundle 或加载本地 AssetBundle 非常有用(将弃用)

    从远程加载 AssetBundle 将自动缓存 AssetBundle:

  • 若 AssetBundle 被压缩,则将启动工作线程来解压缩包并将其写入缓存
  • 若捆绑包被解压缩并缓存,它就会像 AssetBundle.LoadFromFile 一样加载
  • 语法

    1
    2
    3
    4
    //url: 地址
    //version: 版本号
    //crc: crc校验码(选填)
    public static WWW LoadFromCacheOrDownload(string url, int version, uint crc);
  • 由于在 WWW 对象中缓存 AssetBundle 字节所需的内存开销,应该确保AssetBundle 保持较小的大小
  • 在有限内存平台(如移动设备)上运行的开发人员确保其代码一次只下载一个 AssetBundle,以此避免内存峰值
  • 如果缓存文件夹没有任何空间来缓存其他文件,LoadFromCacheOrDownload 将以迭代方式从缓存中删除最近最少使用的 AssetBundle,直到有足够的空间来存储新的 AssetBundle
  • 如果无法腾出空间(因为硬盘已满,或者缓存中的所有文件当前都处于使用状态),LoadFromCacheOrDownload() 将不会使用缓存,而将文件流式传输到内存中
  • 为了强制执行 LoadFromCacheOrDownload,需要更改版本参数(第二个参数)。仅当传递给函数的版本与当前缓存的 AssetBundle 的版本匹配,才会从缓存加载 AssetBundle
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using UnityEngine;

    public class LoadFromFile : MonoBehaviour
    {
    private string path = "Assets/AssetBundles/scene/cubewall";//预制体的路径
    private void Start()
    {
    StartCoroutine("WWWWLoadFromLocal");
    }
    #region WWW方式
    IEnumerator WWWWLoadFromLocal()
    {
    while (!Caching.ready)
    {
    yield return null;
    }
    //从本地加载
    //WWW www = WWW.LoadFromCacheOrDownload(@"file://F:\KOONick\unity3D\AssetBundlePro\Assets\AssetBundles\scene\cubewall", 1);
    //从服务器中加载(这里把服务器弄到本地上)
    WWW www = WWW.LoadFromCacheOrDownload(@"http://localhost/AssetBundles/scene/cubewall", 1);
    yield return www;
    if (!string.IsNullOrEmpty(www.error))
    {
    Debug.Log(www.error);
    yield break;
    }
    AssetBundle ab = www.assetBundle;
    if (ab == null)
    {
    Debug.Log("加载失败");
    }
    else
    {
    GameObject obj = ab.LoadAsset<GameObject>("Cube");
    Instantiate(obj);
    }
    }
    #endregion
    }

    UnityWebRequest

    语法

    1
    2
    3
    4
    //uri: 地址
    //version: 版本号
    //crc: crc校验码(选填)
    UnityWebRequest UnityWebRequestAssetBundle.GetAssetBundle(Uri uri, uint version, uint crc);

    使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;

    public class LoadFromFile : MonoBehaviour
    {
    private string path = "Assets/AssetBundles/scene/cubewall";//预制体的路径
    private void Start()
    {
    StartCoroutine("UnityWebRequestFunc");
    }
    #region UnityWebRequest方式
    IEnumerator UnityWebRequestFunc()
    {
    //从本地加载(地址也可以写: @"file://F:\KOONick\unity3D\AssetBundlePro\Assets\AssetBundles\scene\cubewall")
    //string uri = "file:///" + Application.dataPath + "/AssetBundles/scene/cubewall";

    //从服务器加载
    string uri = @"http://localhost/AssetBundles/scene/cubewall";
    UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, 0);
    yield return request.SendWebRequest();
    AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
    if (ab == null)
    {
    Debug.Log("加载失败");
    }
    else
    {
    GameObject obj = ab.LoadAsset<GameObject>("Cube");
    Instantiate(obj);
    }
    }
    #endregion
    }

    加载资源

    同步加载

    加载单个游戏对象
    1
    GameObject obj = ab.LoadAsset<GameObject>("物体名字");
    加载所有资源
    1
    2
    3
    4
    5
    6
    //把所有资源放在一个object类型的数组中
    object[] objArray = ab.LoadAllAssets();
    foreach(object o in objArray)
    {
    Instantiate(o as GameObject);
    }

    异步加载

    加载单个游戏对象
    1
    2
    3
    4
    AssetBundleRequest ab_request = ab.LoadAssetAsync<GameObject>("Cube_01");
    yield return ab_request;
    var obj = ab_request.asset;
    Instantiate(obj);
    加载所有资源
    1
    2
    3
    4
    5
    6
    7
    AssetBundleRequest ab_request = ab.LoadAllAssetsAsync();
    yield return ab_request;
    object[] objArray = ab_request.allAssets;
    foreach (object o in objArray)
    {
    Instantiate(o as GameObject);
    }

    卸载

    描述

    卸载成功可以减少内存使用,但是卸载失败有可能导致丢失

    AssetBundle.Unload(false)

    卸载所有没有被使用的资源

  • 若有个包的某个资源正在被使用,则这个包会被卸载,而正在使用的资源不会被卸载,它们断开联系
  • 重新导入这个包,其也不会与之前的资源产生联系
  • 若这个资源不再被使用,由于它与之前的包断开联系,则会占用内存
  • 只能以两种方式卸载单个对象:

  • 在场景和代码中消除对不需要的对象的所有引用,调用Resources.UnloadUnusedAssets.
  • 切换场景,则当前场景中的所有对象将自动调用Resources.UnloadUnusedAssets.
  • AssetBundle.Unload(true)

    卸载所有资源,即使有资源被使用着

    文件校验

    有CRC、MD5、SHA1三种

    相同点:

    CRC、MD5、SHA1都是通过对数据进行计算,生成一个校验值,该校验值用来校验数据的完整性

  • 算法不同CRC采用多项式除法,MD5和SHA1使用的是替换、轮转等方法
  • 校验值的长度不同。CRC校验位的长度跟其多项式有关系,一般为16位或32位;MD5是16个字节(128位);SHA1是20个字节(160位)
  • 校验值的称呼不同。CRC一般叫做CRC值;MD5和SHA1一般叫做哈希值(Hash)或散列值
  • 安全性不同。这里的安全性是指检错的能力,即数据的错误能通过校验位检测出来。CRC的安全性跟多项式有很大关系,相对于MD5和SHA1要弱很多;MD5的安全性很高;SHA1的安全性最高。
  • 效率不同。CRC的计算效率很高;MD5和SHA1比较慢。
  • 用途不同。CRC一般用作通信数据的校验;MD5和SHA1用于安全(Security)领域,比如文件校验、数字签名等
  • 修补

    只需要下载新的 AssetBundle 并替换现有的 AssetBundle。如果使用 WWW.LoadFromCacheOrDownload UnityWebRequest 来管理应用程序的缓存 AssetBundle,则将不同的版本参数传递给所选 API 将触发新 AssetBundle 的下载

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

  • 当前已下载的 AssetBundle 及其版本控制信息的列表
  • 服务器上的 AssetBundle 及其版本控制信息的列表
  • 浏览工具

    可以使用unity官方的插件AssetBundles-Browser

    对项目的AssetBundle进行管理

    AssetBundle_02

    对项目的AssetBundle进行打包

    AssetBundle_03

  • 本文作者: zhaoo
  • 本文链接: http://koonick.gitee.io/koonick-blog/2020/12/10/Unity_AssetBundles/index.html
  • 版权声明: 本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!
  •