Nobollel開発者ブログ

Nobollelのエンジニアが、UnityやCocos2d-xの旬な情報・技術を紹介します。

UniRxを使ったダウンロード時のプログレスバーの実装

こんにちは、Nobollel株式会社でエンジニアをしている土本です。

Nobollelではモバイルゲーム開発にUnityを使っていて、RxのライブラリとしてUniRxを積極的に使用しています。

みなさんは複数URLのコンテンツダウンロードのプログレスバーやダウンロードが完了した個数の表示はどのように実装していますでしょうか?

今回はゲーム起動時にアセットバンドルを連続してダウンロードし、プログレスバーに進捗を反映させるときに役に立つtipsを紹介したいと思います。

UniRxでいくつものURLに連続してHTTPリクエストをする場合、WWWObservableとクエリ構文を使って簡単に実装できます。

IObservable<byte[]> Request(string url) {
    return ObservableWWW.GetAndGetBytes(url);
}

var query = from first in ("assetbundle_url1")
    from second in Request("assetbundle_url2")
    from third in Request("assetbundle_url3")
    select new { first, second, third };
    
var cancel = query.Subscribe(result => OnCompleted(result));

ただしこの方法だと、1番目と2番目のリクエストの完了を検知できません。 そこで、クエリ構文を使わずに書くとこうなります。

void SaveBytes(byte[]) {
    // アセットバンドルのデータを保存
}

void UpdateProgressBar() {
    // プログレスバーの更新
}

Request("assetbundle_url1")
    .SelectMany(bytes => {
        SaveBytes(bytes);
        UpdateProgressBar();
        return Request("assetbundle_url2");
    })
    .SelectMany(bytes => {
        SaveBytes(bytes);
        UpdateProgressBar();
        return Request("assetbundle_url3");
    })
    .Subscribe(bytes => OnCompleted(bytes));

これで、リクエストの完了ごとにプログレスバーの更新ができるようになりました。

予めリクエストする回数が分かっているときはこれで良いのですが、ダウンロードするアセットバンドルの数(リクエスト数)が可変のときは、この方法は使えません。

そこで、次のような方法で可変個数に対応します。

List<string> urls = new List<string>{"url1", "url2", "url3"};
urls.Select(url => {
    return Observable.Defer(() => {
        return Request(url);
    });
}).Aggregate((x, y) => x.SelectMany(bytes => {
    SaveBytes(bytes);
    UpdateProgressBar();
    return y;
})).Subscribe(bytes => {
    SaveBytes(bytes);
    UpdateProgressBar();
});

まず最初のSelectでstringをIObservable<byte[]>に変換します。 そのあとAggregateとSelectManyを使って、2つ目のリクエスト以降を前のリクエストの通知が来たら次のリクエストを開始するように変換します。 このようにすることによって、数珠つなぎのように連続してリクエストを送ることができます。

展開すると以下のような形になります。 (分かりやすくするためレスポンス取得後のSaveBytesとUpdateProgressBarの呼び出しを省略しています)

  • 1回目のxにrequest1が入る
var request1 = Observable.Defer(() => Request(url1));
  • 2回目のxにrequest2が入る
var request2 = request1.SelectMany(_ => Observable.Defer(() => Request(url2)));
  • 3回目のxにrequest3が入る
var request3 = request2.SelectMany(_ => Observable.Defer(() => Request(url3)));

最後にSubscribeします

request1.Subscribe();
request2.Subscribe();
request3.Subscribe();

いかがでしたでしょうか? ダウンロード処理を実装する際、このtipsが参考になれば嬉しいです。

ただいまNobollelではUnityエンジニア、プログラマーを絶賛募集中です。 興味のある方はご連絡ください! nobollel.com