UnityWebRequestのダウンロード進捗率の表示をMV(R)Pで実装する【UniTask 2版】

xrdnk.hateblo.jp

以前,上記の記事を出しました.こちらのUniTask 2版を作りました.

UniTask 2

UniTask ver 2(以下,UniTask 2)が今月頭にリリースされました.

昨日,下記の勉強会にとりすーぷさんが「UniTaskの使い方2020」について発表しておりました.

csharp-tokyo.connpass.com

50分というボリュームで発表されていたので,Unityの非同期処理をやるなら必見.

UniTask 2 qiita まとめ.

qiita.com

使用環境

Unity 2019.4.0f1
UniRx 6.2.2
UniTask 2.0.21

ProgressModel

UniTask と UniTask 2で比較します.

ProgressModel.cs (UniTask)

using System;
using UnityEngine;
using UniRx;
using UniRx.Async;
using UnityEngine.Networking;

public class ProgressModel : MonoBehaviour
{
    private ReactiveProperty<float> _downloadProgress = new ReactiveProperty<float>();
    public IReadOnlyReactiveProperty<float> DownloadProgress => _downloadProgress;

    public async void DownloadContent()
    {
        // 取得したいコンテンツのURL
        var url = "https://public-cdn.cloud.unity3d.com/hub/prod/UnityHubSetup.dmg";
        // テキストデータの取得
        var textData = await DownloadTextAsync(url);
        // テキストデータの表示
        Debug.Log(textData);
    }

    /// <summary>
    /// コンテンツのダウンロード非同期処理
    /// </summary>
    /// <param name="url">URL</param>
    /// <returns>コンテンツのテキストデータ</returns>
    private async UniTask<string> DownloadTextAsync(string url)
    {
        using (UnityWebRequest uwr = UnityWebRequest.Get(url))
        {
            // 送受信開始
            await uwr.SendWebRequest()
                    .ConfigureAwait(Progress.Create<float>(x =>
                        {
                            // 値の変化を設定
                            _downloadProgress.Value = x * 100;
                            // コンソール確認用
                            Debug.Log($"{x * 100} %");
                        }));
            // エラーハンドリング
            if (uwr.isNetworkError || uwr.isHttpError) throw new Exception(uwr.error);
            // ダウンロードしたコンテンツのテキストデータを返す
            return uwr.downloadHandler.text;
        }
    }
}

ProgressModel.cs (UniTask 2)

using System;
using UnityEngine;
using System.Threading;
using Cysharp.Threading.Tasks; // UniTask 2 から名前空間が UniRx.Async から変更
using UniRx;
using UnityEngine.Networking;

public class ProgressModel : MonoBehaviour
{
    private ReactiveProperty<float> _downloadProgress = new ReactiveProperty<float>();
    public IReadOnlyReactiveProperty<float> DownloadProgress => _downloadProgress;

    // CancellationTokenの設定
    private CancellationToken _token;

    private void Awake()
    {
        // Destroy時にキャンセルされるCancellationTokenを取得
        _token = this.GetCancellationTokenOnDestroy();
    }

    public async void DownloadContent()
    {
        // 取得したいコンテンツのURL
        var url = "https://public-cdn.cloud.unity3d.com/hub/prod/UnityHubSetup.dmg";
        // テキストデータの取得
        var textData = await DownloadTextAsync(url);
        // テキストデータの表示
        Debug.Log(textData);
    }

    /// <summary>
    /// コンテンツのダウンロード処理
    /// </summary>
    /// <param name="url">URL</param>
    /// <returns>コンテンツのテキストデータ</returns>
    private async UniTask<string> DownloadTextAsync(string url)
    {
        using (var uwr = UnityWebRequest.Get(url))
        {
            // 送受信開始
            await uwr.SendWebRequest()
                        .ToUniTask(Progress.Create<float>(x =>
                                // 値の変化を設定
                                _downloadProgress.Value = x * 100),
                                // cancellationToken: のラベルはつけること
                                cancellationToken: _token);
            // エラーハンドリング
            if (uwr.isNetworkError || uwr.isHttpError) throw new Exception(uwr.error);
            // ダウンロードしたコンテンツのテキストデータを返す
            return uwr.downloadHandler.text;
        }
    }
}

変更点

名前空間の変更

名前空間UniRx.Async から Cysharp.Threading.Tasks に変更されてます.

フォルダの階層も変わってますね.

f:id:xrdnk:20200627225227p:plain

今までは Assets > UniRx.Asyncに配置されてましたが,
UniTask 2から Assets > Plugins > UniTaskになりました.

AsyncOperationをawaitするときのConfigureAwait廃止

qiita.com

上の記事にも書かれておりますが,AsyncOperationをawaitするときに利用できていたConfigureAwaitが廃止.
実際にやってみるとこんなエラーが出ます.

f:id:xrdnk:20200627225444p:plain

ToUniTask()を利用します.
引数の順番的にCancellationTokenを引数に持つためには,ラベル(cancellationToken:)をつけるのが必要です.
つけないとエラーが出ます.

ProgressPresenter と ProgressView

ProgressViewとProgressPresenterはver 1と変わりません.

ProgressView.cs

using System; // IObservableの利用に必要
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class ProgressView : MonoBehaviour
{
    [SerializeField, Tooltip("ダウンロード進捗率表示用のUI")]
    private Text _text;
    [SerializeField, Tooltip("ダウンロードボタン")]
    private Button _button;

    private IObservable<Unit> _onDownloadButtonPushed => _button.OnClickAsObservable();
    public IObservable<Unit> OnDownloadButtonPushed => _onDownloadButtonPushed;

    /// <summary>
    /// ダウンロード進捗率を整数表示する
    /// </summary>
    /// <param name="progress"></param>
    public void DisplayProgress(float progress)
    {
        _text.text = progress.ToString("F0") + " %";
    }
}

ProgressPresenter.cs

using UnityEngine;
using UniRx;

[RequireComponent(typeof(ProgressModel))]
[RequireComponent(typeof(ProgressView))]
public class ProgressPresenter : MonoBehaviour
{
    // Extenjectを用いる方が良い
    [SerializeField] private ProgressModel _progressModel;
    [SerializeField] private ProgressView _progressView;

    void Start()
    {
        // Viewにあるダウンロードボタンの押下時、Modelに通知する
        _progressView.OnDownloadButtonPushed.Subscribe(_ => _progressModel.DownloadContent());

        // Modelにある進捗率の値が変化した時、Viewに通知する
        _progressModel.DownloadProgress.Subscribe(_progressView.DisplayProgress);
    }
}

動作確認

f:id:xrdnk:20200627225728g:plain

無事動きました.
引き続きUniTask 2の資料を読んで使いこなしたいと思います.