【Unity】【Addressable】アセットバンドルのダウンロード・ロード処理をカスタムする

UnityのAddressableアセットシステムでアセットバンドルのダウンロード・ロード処理をカスタムする方法をまとめました。

Unity2019.2.10
Addressable1.3.8

はじめに

この記事ではAddressableでアセットバンドルのダウンロード・ロード処理をカスタムする方法についてまとめます。
Addressableの概念や基礎知識についての説明はこの記事では省略しますが、
以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

ダウンロード・ロード処理をカスタム

Addressableアセットシステムには、UnityWebRequestAssetBundleを使ってアセットバンドルの
ダウンロードやロードの管理をするProviderと呼ばれるクラスが標準で用意されています。

しかしUnityWebRequestAssetBundleを使うとAssetBundleのキャッシュ機構をUnityに任せることになります。
UnityWebRequestAssetBundleで要件を満たせれば問題はありませんが、例えばリソースの暗号化などができません。
このあたりの詳細は以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

light11.hatenadiary.com

さてAddressableアセットシステムでUnityWebRequestAssetBundle以外を使ってダウンロードしたい場合には
独自のProviderを作成してダウンロードやロードの処理をカスタムする必要があります。

本記事ではこの方法についてまとめます。

ソースコード

それでは早速ですが、まずカスタムProviderのソースコード全文を記載します。

using System;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;

public class CustomAssetBundleResource : IAssetBundleResource
{
    private AssetBundle _assetBundle;
    private DownloadHandlerAssetBundle _downloadHandler;
    private AsyncOperation _requestOperation;
    private ProvideHandle _provideHandle;
    private AssetBundleRequestOptions _options;
    private int _retryCount;

    /// <summary>
    /// 初期化する
    /// </summary>
    public void Setup(ProvideHandle provideHandle)
    {
        _retryCount = 0;
        _assetBundle = null;
        _downloadHandler = null;
        _provideHandle = provideHandle;
        _options = _provideHandle.Location.Data as AssetBundleRequestOptions;
        _requestOperation = null;
        provideHandle.SetProgressCallback(GetProgress);
    }

    /// <summary>
    /// ロード・ダウンロードする
    /// </summary>
    public void Fetch()
    {
        var location = _provideHandle.Location;
        var url = location.InternalId;

        // UnityWebRequestを生成
        var webRequest = !string.IsNullOrEmpty(_options.Hash) ?
            UnityWebRequestAssetBundle.GetAssetBundle(url, Hash128.Parse(_options.Hash), _options.Crc) :
            UnityWebRequestAssetBundle.GetAssetBundle(url, _options.Crc);
        if (_options.Timeout > 0) {
            webRequest.timeout = _options.Timeout;
        }
        if (_options.RedirectLimit > 0) {
            webRequest.redirectLimit = _options.RedirectLimit;
        }
#if !UNITY_2019_3_OR_NEWER
        webRequest.chunkedTransfer = _options.ChunkedTransfer;
#endif
        if (_provideHandle.ResourceManager.CertificateHandlerInstance != null) {
            webRequest.certificateHandler = _provideHandle.ResourceManager.CertificateHandlerInstance;
            webRequest.disposeCertificateHandlerOnDispose = false;
        }
        webRequest.disposeDownloadHandlerOnDispose = false;

        // UnityWebRequestを送信
        _requestOperation = webRequest.SendWebRequest();
        _requestOperation.completed += op =>
        {
            var webReq = (op as UnityWebRequestAsyncOperation).webRequest;
            if (string.IsNullOrEmpty(webReq.error)) {
                // 成功時の処理
                _downloadHandler = webReq.downloadHandler as DownloadHandlerAssetBundle;
                _provideHandle.Complete(this, true, null);
            }
            else {
                // エラーがあった場合の処理
                _downloadHandler = webReq.downloadHandler as DownloadHandlerAssetBundle;
                _downloadHandler.Dispose();
                _downloadHandler = null;
                if (_retryCount >= _options.RetryCount) {
                    // エラー終了
                    var exception = new Exception($"Download failed : {webReq.url}");
                    _provideHandle.Complete<CustomAssetBundleResource>(null, false, exception);
                }
                else {
                    // リトライ
                    Fetch();
                    _retryCount++;
                }
            }
            webReq.Dispose();
        };
    }

    /// <summary>
    /// アンロードする
    /// </summary>
    public void Unload()
    {
        if (_assetBundle != null) {
            _assetBundle.Unload(true);
            _assetBundle = null;
        }
        if (_downloadHandler != null) {
            _downloadHandler.Dispose();
            _downloadHandler = null;
        }
        _requestOperation = null;
    }

    /// <summary>
    /// ロード・ダウンロードされたAssetBundleを取得する
    /// </summary>
    public AssetBundle GetAssetBundle()
    {
        if (_assetBundle == null && _downloadHandler != null) {
            _assetBundle = _downloadHandler.assetBundle;
            _downloadHandler.Dispose();
            _downloadHandler = null;
        }
        return _assetBundle;
    }

    /// <summary>
    /// ロード・ダウンロード進捗を取得する
    /// </summary>
    private float GetProgress() => _requestOperation != null ? _requestOperation.progress : 0.0f;
}

[System.ComponentModel.DisplayName("Custom AssetBundle Provider")]
public class CustomAssetBundleProvider : ResourceProviderBase
{
    /// <summary>
    /// ProvideHandleに入っている情報が示すAssetBundleを読み込む処理
    /// </summary>
    public override void Provide(ProvideHandle providerInterface){
        var res = new CustomAssetBundleResource();
        res.Setup(providerInterface);
        res.Fetch();
    }

    /// <summary>
    /// 読み込み結果としてAssetロード用のProviderに渡すための型を返却
    /// Assets from Bundles Providerを使う場合にはIAssetBundleResourceを指定
    /// </summary>
    public override Type GetDefaultType(IResourceLocation location) => typeof(IAssetBundleResource);

    /// <summary>
    /// 解放処理
    /// </summary>
    public override void Release(IResourceLocation location, object asset)
    {
        if (location == null)
            throw new ArgumentNullException("location");
        if (asset == null) {
            Debug.LogWarningFormat("Releasing null asset bundle from location {0}.  This is an indication that the bundle failed to load.", location);
            return;
        }
        var bundle = asset as CustomAssetBundleResource;
        if (bundle != null) {
            bundle.Unload();
            return;
        }
        return;
    }
}

細かい説明はコメントに書いた通りなので省略します。

大枠として、独自のProviderを作るにはResourceProviderBaseを継承したクラスを作ります。
ダウンロードやロードがリクエストされるとProvide()が呼ばれるのでここにダウンロード・ロード処理を書きます。

アンロード時にはRelease()が呼ばれるのでここにアンロード処理を書きます。

Providerを設定する

あとはこのProviderを使うように設定するだけです。
ProviderはAssetGroupのInspectorから設定できます。

AssetBundle Providerから先ほど作ったProviderが選択できるので、これを選択すればOKです。

f:id:halya_11:20191120134101p:plain

これでアセットバンドルのダウンロード・ロード処理がカスタムできました。

関連

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com