AssetBundle系列(一)

AssetBundle简介

Unity中的资源加载方式

场景资源

在场景中被直接引用的资源可以不用打包,将直接进包,随场景加载,但并不推荐

Resouorces

Resuorces资源存放于Unity工程Asset目录中任意Resources目录下。Resuorces目录中的资源不论是否使用都会打包进入Apk中,并且会被加密。

加载方式:
  1. Resources目录下的资源可以通过Resources.Load加载;
  2. Resources类也提供了Resources.LoadAssetAtPath方法在编辑器下加载资源,但该方法已过时,被 AssetDatabase.LoadAssetAtPath 替代;

官方并不推荐Resources方式存储和加载资源。因为它有明显的缺点:

缺点:
  1. Resources目录的资源会增加应用程序的启动时间和构建时长(构建项目的时候,所有的Resources目录下的文件会被合并为一个序列化文件);
  2. Resources目录的资源无法增量更新;
AssetBundle

AssetBundle是预先将资源按照平台类型打包为二进制 文件,并在运行时动态加载的一种资源组织方式。

AssetBundle可以通过网络下载对游戏中的资源进行热更新,是官方推荐的资源方式。

优点
  1. 动态加载释放资源
  2. 不重装的情况下更新资源(热更)
  3. 为不同的用户配置不同的资源包(平台、语言)
缺点
  1. 资源为二进制,不够直观
  2. 调试的时候无法直接定位到资源
  3. 需要花费时间处理资源分包以及打包
AssetDatabase

AssetDatabase可以在编辑器下动态加载资源,通过AssetDatabase可以更好的在编辑器下进行调试,而不需要频繁打包AssetBundle。

加载方式 :
  1. 通过AssetDatabase.LoadAssetAtPath方法加载;

AssetBundle打包

AssetBundle资源包

前面说过,AssetBundle都需要将资源打AB包。打完的AB包包含资源的压缩二进制文件和它的同名.manifest文件。

.manifest文件

manifest文件保存了AB包中包含的资源,以及其依赖的其他AB包,如下

...
Assets:
- Assets/GameResources/Prefabs/Humans/QS02.prefab
- Assets/GameResources/Prefabs/Humans/QS01.prefab
Dependencies:
- /Users/gjy/Github/Study/AcgSample/_Assetbundles/iqs01
- /Users/gjy/Github/Study/AcgSample/_Assetbundles/mqs01

其中QS01.prefab、QS02.prefab是AB包中包含的资源,mqs01和iqs01是被单独打包的另两个AB资源包;

AB包压缩格式
压缩算法BuildAssetBundleOptions说明
LZMA算法
None压缩的包更小,但是加载时间更长。使用之前需要整体解压。一旦被解压,这个包会使用LZ4重新压缩。使用资源的时候不需要整体解压。在下载的时候可以使用LZMA算法,一旦它被下载了之后,它会使用LZ4算法保存到本地上
LZ4压缩
ChunkBasedCompression
压缩率没有LZMA高,但是我们可以加载指定资源而不用解压全部
不压缩UncompressedAssetBundle包体大,加载速度快
AssetBundle打包
标记资源

在资源Asset的Inspector面板最下方,可以设置AssetBundle路径名字,名字相同的资源将被打进同一个assetbundle;

可以通过代码设置AssetBundle

string resPath = "Assets/GameResources/Prefabs/Humans/QS01.prefab";

//AssetImporter是用于资源导入的派生类的基类
AssetImporter.GetAtPath(resDir).SetAssetBundleNameAndVariant("QS01", string.Empty);
打包

使用BuildPipeline.BuildAssetBundles来进行打包

//打包所有被标记名字的AssetBundle
//outputPath : ab输出路径
//assetBundleOptions: 压缩格式
//targetPlatform : 目标平台
public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);

//打包指定的AssetBundle
//builds : 需要被打包的资源
public static AssetBundleManifest BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
资源显式进AB包和隐式进AB包
  • 显式进AB包,指的是被设置了AssetBundle路径名字的资源将一定被打进AB包中;
  • 隐式进AB包,为了防止在打包时遗漏资源或方便分包,Unity会将没有被设置路径名字,但被其他显式进包资源的引用的资源,一起打进AB包中。但是每个引用这个资源的AB包都会包含一份该资源;
常见的分包策略

为了防止资源冗余,利用好AssetBundle的优点,我们需要分包策略:

  1. 逻辑实体分组,同一个界面或同一个角色的资源等分为一个AB包,适合DLC等下载内容;
  2. 类型分组,同类资源如音频、语言包等,适合在多个平台使用的AssetBundle;
  3. 并发内容分组,是指将需要同时加载和使用的资源捆绑在一起。例如同一个关卡,同一个场景等需要一起出现的资源;

AssetBundle资源加载(官方文档)

AB包加载

一共有四种加载AB包的方法,根据AB包的平台和构建时选择的压缩方法而有所不同。

AssetBundle.LoadFromMemoryAsync

此函数采用包含 AssetBundle 数据的字节数组。也可以根据需要传递 CRC 值。如果捆绑包采用的是 LZMA 压缩方式,将在加载时解压缩 AssetBundle。LZ4 压缩包则会以压缩状态加载。

官网示例

using UnityEngine;
using System.Collections;
using System.IO;

public class Example : MonoBehaviour
{
    IEnumerator LoadFromMemoryAsync(string path)
    {
        AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
        yield return createRequest;
        AssetBundle bundle = createRequest.assetBundle;
        var prefab = bundle.LoadAsset<GameObject>("MyObject");
        Instantiate(prefab);
    }
}

File.ReadAllBytes(path) 可以替换为获得字节数组的任何所需过程

AssetBundle.LoadFromFile

AssetBundle.LoadFromFile仅会为我们读取AssetBundle的header部分,并不会将bundle的data部分整个读入内存。当调用上一步生成的AssetBundle对象读取具体资源时(LoadAsset, LoadAssetAsync, LoadAllAssets),Unity会参考已经缓存的文件列表,找到目标资源在data部分的位置并读入到内存中。

从本地存储中加载未压缩的捆绑包时,此 API 非常高效。如果捆绑包未压缩或采用了数据块 (LZ4) 压缩方式,LoadFromFile 将直接从磁盘加载捆绑包。使用此方法加载完全压缩的 (LZMA) 捆绑包将首先解压缩捆绑包,然后再将其加载到内存中。

官网示例

using System.IO;
using UnityEngine;

public class LoadFromFileExample : MonoBehaviour
{
    void Start()
    {
        var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));

        if (myLoadedAssetBundle == null)
        {
            Debug.Log("Failed to load AssetBundle!");
            return;
        }
        var prefab = myLoadedAssetBundle.LoadAsset<GameObject>("MyObject");
        Instantiate(prefab);
    }
}
UnityWebRequestAssetBundle

提供了一个从网络下载AB包的方法,结合DownloadHandlerAssetBundle使用,在下载之后加载资源

IEnumerator InstantiateObject()
{
    string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName; 
    UnityEngine.Networking.UnityWebRequestAssetBundle request 
        = UnityEngine.Networking.UnityWebRequestAssetBundle.GetAssetBundle(uri, 0);
    yield return request.SendWebRequest();
    AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
    GameObject cube = bundle.LoadAsset<GameObject>("Cube");
    GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");
    Instantiate(cube);
    Instantiate(sprite);
}
WWW.LoadfromCacheOrDownload

旧版本加载AB包的方式

从AB包加载资源
//加载单个资源
T objectFromBundle = bundleObject.LoadAsset<T>(assetName);

//加载AB包中所有的资源
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();

//异步加载单个资源
AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;

//异步加载所有资源
AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;
加载AB的manifest文件
  1. 通过AssetBundle的LoadAsset方法获取AssetBundleManifest对象;
  2. 通过AssetBundleManifest对象的方法GetAllAssetBundles和GetAllDependencies可以拿到manifest文件中保存的资源路径及依赖包;
//获取manifest
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

//获取依赖
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies("assetBundle"); //传递想要依赖项的捆绑包的名称。
foreach(string dependency in dependencies)
{
    AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}

AssetBundle资源管理(官方文档)

从活动场景中删除对象时,Unity 不会自动卸载对象。资源清理在特定时间触发,也可以手动触发。

了解何时加载和卸载 AssetBundle 非常重要。不正确地卸载 AssetBundle 会导致在内存中复制对象或其他不良情况,例如缺少纹理。

//卸载AssetBundle
AssetBundle.Unload (bool)

//异步卸载AssetBundle
AssetBundle.Unloadasync (bool)

关于AssetBundle管理,需要了解的最重要的事情是什么时候调用AssetBundle.Unload (bool) -或者AssetBundle.Unloadasync (bool) -以及你是否应该将true或false传递给函数调用。Unload是一个非静态函数,它将卸载你的AssetBundle。这个API会卸载被调用的AssetBundle的头信息。该参数指示是否也卸载从这个AssetBundle实例化的所有对象。

AssetBundle.Unload(true) 卸载从 AssetBundle 加载的所有游戏对象(及其依赖项)。这不包括复制的游戏对象(例如实例化的游戏对象),因为它们不再属于 AssetBundle。发生这种情况时,从该 AssetBundle 加载的纹理(并且仍然属于它)会从场景中的游戏对象消失,因此 Unity 将它们视为缺少纹理。

例如我们假设材质M从AssetBundle AB中加载,并用于预制P中。

  • 调用 AB.Unload(true),活动场景中的任何 M 实例也将被卸载并销毁;
  • 调用 AB.Unload(false),那么将会中断 M 和 AB 当前实例的链接关系。在下次加载AB时会加载一个新的M,造成对象重复;

通常,使用AssetBundle.Unload(false)不会带来理想的结果。大多数项目都应该使用AssetBundle.Unload(true),并采用一个方法来确保对象不重复。两种常见的方法是:

  1. 在应用程序生命周期中具有明确定义的卸载瞬态 AssetBundle 的时间点,例如在关卡之间或在加载屏幕期间。
  2. 维护单个对象的引用计数,仅当未使用所有组成对象时才卸载 AssetBundle。这允许应用程序卸载和重新加载单个对象,而无需复制内存。

如果应用程序必须使用 AssetBundle.Unload(false),则只能以两种方式卸载单个对象:

  1. 在场景和代码中消除对不需要的对象的所有引用。完成此操作后,调用 Resources.UnloadUnusedAssets;
  2. 以非Addition方式加载场景。这样会销毁当前场景中的所有对象并自动调用 Resources.UnloadUnusedAssets。

文章引用了官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值