Version: 2023.1
Output of the Build
AssetBundle compression and caching

本机使用 AssetBundle

There are two different APIs that you can use to load AssetBundles.

AssetBundle.LoadFromFile

AssetBundle.LoadFromFile

This API is highly-efficient when loading uncompressed and chunk compressed bundles (LZ4) from local storage because it can read content of the file incrementally, directly from disk. Loading a file that is compressed with full file compression (LZMA) using this method is less efficient because it requires that the file be fully decompressed into memory first.

如何使用 LoadFromFile 的一个示例:

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);
    }
}

Rather than blocking your app during the load process you can also use the asynchronous version, AssetBundle.LoadFromFileAsync.

There are several other methods available to load an AssetBundle if it is not actually stored in a local file. For example you can use AssetBundle.LoadFromMemoryAsync.

UnityWebRequestAssetBundle

The UnityWebRequestAssetBundle API can be called to create a special web request for downloading, caching and loading AssetBundles.

Typically the URL would be a ‘https://’ address pointing to the file as exposed by a web service. It could also be ‘file://’ to access local data on platforms that do not support direct file system access.

This request can be passed into DownloadHandlerAssetBundle.GetContent(UnityWebRequestAssetBundle) to get the AssetBundle object for the loaded AssetBundle.

For example

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class DownloadExample : MonoBehaviour
{
    IEnumerator Start()
    {
        string uri = "https://myserver/myBundles/bundle123"; 
        uint crc = 1098980; // Expected content CRC

        UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, crc);
        yield return request.SendWebRequest();

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
        var loadAsset = bundle.LoadAssetAsync<GameObject>("Assets/Players/MainPlayer.prefab");
        yield return loadAsset;

        Instantiate(loadAsset.asset);
    }
}

For simplicity the example shows a CRC value that is hardcoded in the code. But in practice the expected CRC values for your AssetBundles would be downloaded separately, or retrieved from a file, prior to downloading the AssetBundles. See AssetBundle Download Integrity and Security.

Note: In order to avoid downloading the entire AssetBundle content each time this code is called you could enable caching for your AssetBundle. This is done by passing the AssetBundle hash when calling UnityWebRequestAssetBundle.GetAssetBundle. The hash is available from the AssetBundle Manifest, which is described later in this section. The hash acts as version identifier for the precise build of the AssetBundle that you are requesting. See AssetBundle compression and caching for details of the Cache and the associated considerations about compression.

Loading Assets from AssetBundles

Now that you’ve successfully loaded your AssetBundle, it’s time to finally load in some Assets.

通用代码片段:

T objectFromBundle = bundleObject.LoadAsset<T>(assetName);

T 是尝试加载的资源类型。

决定如何加载资源时有几个选项。我们有 LoadAssetLoadAllAssets 及其各自的异步对应选项 LoadAssetAsyncLoadAllAssetsAsync

同步从 AssetBundle 加载资源的方法如下:

To load a single Asset (for example the root GameObject of a Prefab):

GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);

加载所有资源:

Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();

This returns an array with all the root Object of each Asset.

Now, whereas the previously shown methods return either the type of object you’re loading or an array of objects, the asynchronous methods return an AssetBundleRequest. You’ll need to wait for this operation to complete before accessing the asset. To load an asset asynchronously:

AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;

以及

AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;

加载资源后,就可以开始了!可以像使用 Unity 中的任何对象一样使用加载的对象。

加载 AssetBundle 清单

加载 AssetBundle 清单可能非常有用。特别是在处理 AssetBundle 依赖关系时。

要获得可用的 AssetBundleManifest 对象,需要加载另外的 AssetBundle(与其所在的文件夹名称相同的那个)并从中加载 AssetBundleManifest 类型的对象。

加载清单本身的操作方法与 AssetBundle 中的任何其他资源完全相同:

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

现在,可以通过上面示例中的清单对象访问 AssetBundleManifest API 调用。从这里,可以使用清单获取所构建的 AssetBundle 的相关信息。此信息包括 AssetBundle 的依赖项数据、哈希数据和变体数据。

The earlier section on AssetBundle Dependencies discussed how, in order to load an Asset from an AssetBundle, you must first load any AssetBundles that the AssetBundle depends on. The manifest object makes dynamically finding and loading dependencies possible, so you do not have to hardcode all the AssetBundle names and their relationships explicitly in your code. Let’s say we want to load all the dependencies for an AssetBundle named “assetBundle”.

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、AssetBundle 依赖项和资源,因此是时候讨论如何管理所有这些已加载的 AssetBundle 了。

管理已加载的 AssetBundle

Note: The Addressables package provides a ready-made system to manage loading AssetBundles, dependencies, and Assets. Unity recommends using Addressables rather than managing AssetBundles yourself.

另请参阅:Unity 学习教程 - 管理已加载的 AssetBundle (Managing Loaded AssetBundles)

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

It is important to know when to load and unload an AssetBundle. Improperly unloading an AssetBundle can lead to duplicating objects in memory or missing objects.

The biggest things to understand about AssetBundle management is when to call AssetBundle.Unload(bool) – or AssetBundle.UnloadAsync(bool) – and if you should pass true or false into the function call. Unload is a non-static function that will unload your AssetBundle by removing the AssetBundle header and other data structures from memory. The argument indicates whether to also unload all Objects instantiated from this AssetBundle.

AssetBundle.Unload(true) unloads all GameObjects (and their dependencies) that were loaded from the AssetBundle. This does not include objects that you have copied into your scene (e.g. by calling Instantiate) because that copied data does not belong to the AssetBundle. If the objects in your scene references Textures that were loaded from the AssetBundle (and still belong to it), then this call will cause the Textures to disappear, and Unity treats them as missing Textures.

As an example, let’s assume Material M is loaded from AssetBundle AB as shown below and used in Prefab P.

If AB.Unload(true) is called then any instance of M referenced from objects in the active scene will be destroyed.

If you were instead to call AB.Unload(false) then the instances of M will remain in memory. They will become independent objects that are not linked back to the original AssetBundle.

If AB is loaded again later Unity will not re-link the existing copies of M to the Material inside the AssetBundle.

If you create another instance of Prefab P, it will not use the existing copy of M. Instead a second copy of M is loaded.

Generally, using AssetBundle.Unload(false) can lead to duplicated objects and other issues. Most projects should use AssetBundle.Unload(true) to avoid this. There are two common strategies for safely calling AssetBundle.Unload(true):

  • 在应用程序生命周期中具有明确定义的卸载瞬态 AssetBundle 的时间点,例如在关卡之间或在加载屏幕期间。

  • 维护单个对象的引用计数,仅当未使用所有组成对象时才卸载 AssetBundle。这允许应用程序卸载和重新加载单个对象,而无需复制内存。

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

  • Eliminate all references to an unwanted Object, e.g. make sure no other loaded object has a field that points to it. After this is done, call Resources.UnloadUnusedAssets.

  • 以非附加方式加载场景。这样会销毁当前场景中的所有对象并自动调用 Resources.UnloadUnusedAssets

Output of the Build
AssetBundle compression and caching