集成 Asset Delivery (Unity)

集成 Asset Delivery 时,Unity 游戏可以使用 Addressables 或 AssetBundle 访问资源包。Addressables 是最近推出且推荐使用的 Asset Delivery 解决方案,适用于使用 Unity 2019.4 或更高版本构建的游戏;AssetBundle 则支持 Unity 2017.4 和 2018.4 中的资源包。

Unity Addressables

使用 Unity 2019.4 或更高版本构建的游戏应将 Addressables 用于 Android 设备上的 Asset Delivery。Unity 会提供 Play Asset Delivery (PAD) API,以便使用 Addressables 来处理 Android 资源包。如需了解如何使用 Addressables,请参阅以下资源:

使用 AssetBundle 文件

使用 Unity 2017.4 和 2018.4 构建的游戏可以将 AssetBundle 文件用于 Android 设备上的资源传送。Unity AssetBundle 文件包含可在应用运行时由 Unity 引擎加载的序列化资源。这些文件是平台专用的(例如,针对 Android 构建),并且可与资源包结合使用。最常见的情况是,将一个 AssetBundle 文件打包成单个资源包,并且资源包与 AssetBundle 使用相同的名称。如果您希望更灵活地创建资源包,请使用 API 配置资源包。

在运行时,可使用 Play Asset Delivery for Unity 类来检索打包在资源包中的 AssetBundle。

前提条件

  1. 设置开发环境:

OpenUPM-CLI

如果您已安装 OpenUPM CLI,则可以使用以下命令安装 OpenUPM 注册表:

openupm add com.google.play.assetdelivery

OpenUPM

  1. 依次选择 Unity 菜单选项 Edit > Project Settings > Package Manager,打开软件包管理器设置

  2. 将 OpenUPM 作为受限注册表添加到 Package Manager 窗口:

    Name: package.openupm.com
    URL: https://package.openupm.com
    Scopes: com.google.external-dependency-manager
      com.google.play.common
      com.google.play.core
      com.google.play.assetdelivery
      com.google.android.appbundle
    
  3. 依次选择 Unity 菜单选项 Window > Package Manager,打开软件包管理器菜单

  4. 将经理范围下拉菜单设置为选择我的注册库

  5. 从软件包列表中选择 Google Play Integrity plugin for Unity 软件包,然后按 Install

从 GitHub 导入

  1. 从 GitHub 下载最新的 .unitypackage 版本。

  2. 导入 .unitypackage 文件,方法是依次选择 Unity 菜单选项 Assets > Import package > Custom Package,然后导入所有项目。

  1. 在 Unity 中创建 AssetBundle

使用界面配置 AssetBundle

  1. 配置资源包中的每个 AssetBundle:

    1. 依次选择 Google > Android App Bundle > Asset Delivery Settings
    2. 如需选择直接包含 AssetBundle 文件的文件夹,请点击 Add Folder

  2. 针对每个 Bundle,将 Delivery Mode 更改为 Install TimeFast FollowOn Demand。解决所有错误或依赖项,然后关闭窗口。

  3. 依次选择 Google > Build Android App Bundle 以构建 App Bundle。

  4. (可选)配置 App Bundle 以支持不同的纹理压缩格式

使用 API 配置资源包

您可以通过能作为自动化构建系统的一部分运行的编辑器脚本配置 Asset Delivery。

使用 AssetPackConfig 类定义需要包含在 Android App Bundle build 中的资源,以及这些资源的分发模式。这些资源包无需包含 AssetBundle。

public void ConfigureAssetPacks {
   // Creates an AssetPackConfig with a single asset pack, named
   // examplePackName, containing all the files in path/to/exampleFolder.
   var assetPackConfig = new AssetPackConfig();
   assetPackConfig.AddAssetsFolder("examplePackName",
                                   "path/to/exampleFolder",
                                   AssetPackDeliveryMode.OnDemand);

   // Configures the build system to use the newly created assetPackConfig when
   // calling Google > Build and Run or Google > Build Android App Bundle.
   AssetPackConfigSerializer.SaveConfig(assetPackConfig);

   // Alternatively, use BundleTool.BuildBundle to build an App Bundle from script.
   BuildBundle(new buildPlayerOptions(), assetPackConfig);
}

在给定 BuildPlayerOptionsAssetPackConfig 的情况下,您还可以在 Bundletool 类中使用静态 BuildBundle 方法生成具有资源包的 Android App Bundle。

如需指导教程,请参阅“在 Unity 游戏中使用 Play Asset Delivery”Codelab

与 Play Asset Delivery Unity API 集成

Play Asset Delivery Unity API 提供了用于请求资源包、管理下载内容和获取资产的功能。请务必先将 Unity 插件添加到您的项目中。

您在该 API 中使用的函数取决于您创建资源包的方式。

如果您使用插件界面创建资源包,请选择插件配置的资源包

如果您使用 API(或插件界面)创建资源包,请选择 API 配置的资源包

无论您希望获取的资源包的分发类型为何,API 都是相似的。这些步骤如以下流程图所示。

针对 API 的资源包流程图

图 1. 获取资源包流程图

检索资源包

导入 Play Asset Delivery 库,然后调用 RetrieveAssetPackAsync() 方法下载资源包(如果磁盘中尚无最新版本的资源包)。

using Google.Play.AssetDelivery;

// After download, the assets and/or AssetBundles contained in the asset pack
// are not loaded into memory.
PlayAssetPackRequest request = PlayAssetDelivery.RetrieveAssetPackAsync(assetPackName);

安装时分发

配置为 install-time 的资源包可以在应用启动后立即使用,但您需要将其资产加载到内存中。请参阅将资产加载到内存中

快速跟进式分发和按需分发

以下几部分适用于 fast-followon-demand 资源包。

查看状态

每个资源包都存储于应用的内部存储空间内单独的文件夹中。使用 isDone() 方法确定资源包是否已下载并可供使用,或者是否发生了错误。

监控下载

查询 PlayAssetPackRequest 对象以监控请求的状态

// Download progress of request, between 0.0f and 1.0f. The value will always be
// 1.0 for assets delivered as install-time.
// NOTE: A value of 1.0 does not mean that the request has completed, only that
// the DOWNLOADING stage is finished.
float progress = request.DownloadProgress;

// Returns the status of the retrieval request.
// If the request completed successfully, this value should be AssetDeliveryStatus.Available.
// If an error occurred, this value should be AssetDeliveryStatus.Failed.

AssetDelivery status = request.Status;
switch(status) {
    case AssetDeliveryStatus.Pending:
        // Asset pack download is pending - N/A for install-time assets.
    case AssetDeliveryStatus.Retrieving:
        // Asset pack is being downloaded and transferred to app storage.
        // N/A for install-time assets.
    case AssetDeliveryStatus.Available:
        // Asset pack is downloaded on disk but NOT loaded into memory.
        // For PlayAssetPackRequest(), this indicates that the request is complete.
    case AssetDeliveryStatus.Failed:
        // Asset pack retrieval failed.
    case AssetDeliveryStatus.WaitingForWifi:
        // Asset pack retrieval paused until either the device connects via Wi-Fi,
        // or the user accepts the PlayAssetDelivery.ShowConfirmationDialog dialog.
    case AssetDeliveryStatus.RequiresUserConfirmation:
        // Asset pack retrieval paused until the user accepts the
        // PlayAssetDelivery.ShowConfirmationDialog dialog.
    default:
        break;
}

// Returns true if status is AssetDeliveryStatus.Available or AssetDeliveryStatus.Failed.
bool done = request.IsDone;

// If AssetDeliveryStatus.Failed, find more info about the error.
AssetDeliveryErrorCode error = request.Error;

下载内容较大

超过 200MB 的资源包可以自动下载,但前提是设备必须已连接到 WLAN。如果用户未连接到 WLAN,PlayAssetPackRequest 状态会设置为 AssetDeliveryStatus.WaitingForWifi,下载也会暂停。在这种情况下,要么等到设备连接到 WLAN 再恢复下载,要么提示用户批准通过移动网络连接下载资源包。

需要用户确认

如果软件包的状态为 AssetDeliveryStatus.RequiresUserConfirmation,则在用户接受显示 PlayAssetDelivery.ShowConfirmationDialog() 的对话框之前,下载不会继续。如果 Play 无法识别应用,可能会出现此状态。请注意,在这种情况下,调用 PlayAssetDelivery.ShowConfirmationDialog() 会导致应用更新。更新后,请重新请求资源。

if(request.Status == AssetDeliveryStatus.RequiresUserConfirmation
   || request.Status == AssetDeliveryStatus.WaitingForWifi) {
    var userConfirmationOperation = PlayAssetDelivery.ShowConfirmationDialog();
    yield return userConfirmationOperation;

    switch(userConfirmationOperation.GetResult()) {
        case ConfirmationDialogResult.Unknown:
            // userConfirmationOperation finished with an error. Something went
            // wrong when displaying the prompt to the user, and they weren't
            // able to interact with the dialog.
        case ConfirmationDialogResult.Accepted:
            // User accepted the confirmation dialog--an update will start.
        case ConfirmationDialogResult.Declined:
            // User canceled or declined the dialog. It can be shown again.
        default:
            break;
    }
}

取消请求(仅限按需)

如果您在资源包下载前需要取消请求,请对 PlayAssetPackRequest 对象调用 AttemptCancel() 方法:

// Will only attempt if the status is Pending, Retrieving, or Available; otherwise
// it will be a no-op.
request.AttemptCancel();

// Check to see if the request was successful by checking if the error code is Canceled.
if(request.Error == AssetDeliveryErrorCode.Canceled) {
    // Request was successfully canceled.
}

将资源加载到内存中

请求完成后,请使用以下函数之一将资产加载到内存中:

异步请求资源包

在大多数情况下,您应使用协程异步请求资源包并监控进度,如下所示:

private IEnumerator LoadAssetPackCoroutine(string assetPackName) {

    PlayAssetPackRequest request =
        PlayAssetDelivery.RetrieveAssetPackAsync(assetPackName);

    while (!request.IsDone) {
        if(request.Status == AssetDeliveryStatus.WaitingForWifi) {
            var userConfirmationOperation = PlayAssetDelivery.ShowConfirmationDialog();

            // Wait for confirmation dialog action.
            yield return userConfirmationOperation;

            if((userConfirmationOperation.Error != AssetDeliveryErrorCode.NoError) ||
               (userConfirmationOperation.GetResult() != ConfirmationDialogResult.Accepted)) {
                // The user did not accept the confirmation. Handle as needed.
            }

            // Wait for Wi-Fi connection OR confirmation dialog acceptance before moving on.
            yield return new WaitUntil(() => request.Status != AssetDeliveryStatus.WaitingForWifi);
        }

        // Use request.DownloadProgress to track download progress.
        // Use request.Status to track the status of request.

        yield return null;
    }

    if (request.Error != AssetDeliveryErrorCode.NoError) {
        // There was an error retrieving the pack. For error codes NetworkError
        // and InsufficientStorage, you may prompt the user to check their
        // connection settings or check their storage space, respectively, then
        // try again.
        yield return null;
    }

    // Request was successful. Load the asset pack into memory.
    AssetBundleCreateRequest assetBundleCreateRequest = request.LoadAssetBundleAsync(path/to/exampleBundle);
    yield return assetBundleCreateRequest;
    AssetBundle assetBundle = assetBundleCreateRequest.assetBundle;

如需详细了解如何处理错误,请参阅错误代码列表。

其他 Play Core API 方法

以下是您可能希望在应用中使用的一些其他 API 方法。

检索多个资源包

如需一次检索多个资源包,请使用以下函数:

// assetPackNames is an array of strings corresponding to asset packs.
PlayAssetPackBatchRequest batchRequest = PlayAssetDelivery.RetrieveAssetPackBatchAsync(<IListstring> assetPackNames);

通过检查状态的 Dictionary 监控每个请求的状态:

// Dictionary of AssetPackStates, with the asset pack name as the key.
Dictionary<string, PlayAssetPackRequest> requests = batchRequest.Requests;

// Returns true if all requests are complete.
bool requestComplete = batchRequest.IsDone;

检查下载内容大小

向 Google Play 发出异步调用并针对操作何时完成设置回调方法,从而检查资源包的大小:

public IEnumerator GetDownloadSize() {
   PlayAsyncOperation<long> getSizeOperation =
   PlayAssetDelivery.GetDownloadSize(assetPackName);

   yield return getSizeOperation;
   if(operation.Error != AssetDeliveryErrorCode.NoError) {
       // Error while retrieving download size.
    } else {
        // Download size is given in bytes.
        long downloadSize = operation.GetResult();
    }
}

移除 AssetBundle

您可以移除当前未加载到内存中的快速跟进式分发和按需分发资源包。发出以下异步调用,并针对何时完成设置回调方法:

PlayAsyncOperation<string> removeOperation = PlayAssetDelivery.RemoveAssetPack(assetBundleName);

removeOperation.Completed += (operation) =>
            {
                if(operation.Error != AssetDeliveryErrorCode.NoError) {
                    // Error while attempting to remove AssetBundles.
                } else {
                    // Files were deleted OR files did not exist to begin with.
                }
            };

后续步骤

在本地和通过 Google Play 测试 Asset Delivery 情况