【cocos2d-x】AssetsManagerExを使って非同期ダウンロード

[元ブログ - 【cocos2d-x】AssetsManagerExを使って非同期ダウンロード - 技術は熱いうちに打て！](http://blog.dalt.me/1224)

皆さんはリソースの管理はどうやっていますでしょうか？
アプリに直接組み込んでいますか？

直接組み込む方式は楽ですが、
大きいデメリットが2点あります。
1. アップデートコスト
2. アプリ容量の肥大化
です。

### 1. アップデートコスト

組み込み型だと、リソースを追加/更新/削除する場合、アップデートしなくてはなりません。
iOSではアプリのアップデートをするのに審査がありますから1週間はかかります。
決まった日時にイベントをやりたい場合、そのイベント日時から逆算して、アップデートをしなければなりません。
審査が通れば良いですが、リジェクトされれば目も当てられませんよね。

### 2. アプリ容量の肥大化

近年のゲームはクオリティがどんどん高くなり、画像の精細化が進んでいます。
こういった画像類をすべて組み込んでアプリをリリースすると数百MBになる、なんてことも。

## AssetsManagerExを使おう

そこで登場するのがAssetsManagerExです。
AssetsManagerExはcocos2d-x v3.3で登場した代物です。
[http://www.cocos2d-x.org/wiki/Assets_manager](http://www.cocos2d-x.org/wiki/Assets_manager)

サーバー側でファイルをちょっと更新するだけでこんな事をやってくれます。


**・リソースのダウンロード(追加/更新/削除)
　- 差分ダウンロードも可
・ダウンロードファイルの端末保存
・進捗
・zipファイルのunzip化
・レジューム**


### リソースをダウンロードするまで

用意するのは2種類(3種類)のマニフェストファイルのみ。
後は実装するだけ！

とりあえず、軽くコードを見てみましょう。

```json:main.manifest
// ローカル
{
    "packageUrl" : "http://dalt.me/assets",
    "remoteVersionUrl" : "http://dalt.me/assets/version.manifest",
    "remoteManifestUrl" : "http://dalt.me/assets/main.manifest",
    "version" : "1.0.0",
    "engineVersion" : "Cocos2d-x v3.9",
    "assets" : {
      "test.png": {
        "md5": "0f70257a53987279299a54ab284a0ff1"
      },
      "test3.png": {
        "md5":"977cb044104b7f4fc925bdf426fb52db"
      }
    }
}
```

```json:main.manifest
// リモート
{
    "packageUrl" : "http://dalt.me/assets",
    "remoteVersionUrl" : "http://dalt.me/assets/version.manifest",
    "remoteManifestUrl" : "http://dalt.me/assets/main.manifest",
    "version" : "1.0.0",
    "engineVersion" : "Cocos2d-x v3.9",
    "assets" : {
      "test.png": {
        "md5": "364be8860e8d72b4358b5e88099a935a"
      },
      "ziptest.zip": {
        "md5":"77901e0eb8ee86990a69e6655fc1528f",
        "compressed": true
      }
    }
}
```

```json:version.manifest
// リモート (必須ではない)
{
  "version": "1.0.0"
}
```

```cpp:TestScene.cpp
void TestScene::onEnterTransitionDidFinish()
{
  // ローカルマニフェストへのパス
    std::string manifestUrl = "main.manifest";

    // 保存先パス
    std::string storagePath = cocos2d::FileUtils::getInstance()->getWritablePath() + "assets";

    // managerインスタンスの生成
    extension::AssetsManagerEx* manager = extension::AssetsManagerEx::create(manifestUrl, storagePath);

    // managerに対するコールバックの設定
    extension::EventListenerAssetsManagerEx* listener = extension::EventListenerAssetsManagerEx::create(manager, [](extension::EventAssetsManagerEx* event)
    {
        // 具体的なコールバック
    });
    Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(listener, 1);

    // ダウンロード開始
    manager->update();
}
```

### リソースの使用

ダウンロードして来たリソースはこの様に使います。

```
cocos2d::Sprite* test = cocos2d::Sprite::create(storagePath + "test.png");
test->setPosition(this->getContentSize() / 2);
this->addChild(test);
```

```
// zipファイルはunzipされています
cocos2d::Sprite* test = cocos2d::Sprite::create(storagePath + "ziptest/test2.png");
test->setPosition(this->getContentSize() / 2);
this->addChild(test);
```

これだけでダウンロードして来た画像を元にSpriteが生成されます。

## 仕組み

ここからはAssetsManagerExがどの様な動きをしているかの仕組みを書きたいとおもいます。
主な流れは以下の様になります。

1. ローカルマニフェスト読み込み
2. バージョンマニフェストダウンロード
3. メインマニフェストダウンロード
4. アセットの追加/更新/削除
5. unzip (必要であれば)
6. メインマニフェストのキャッシュ

### 1. ローカルマニフェスト読み込み

端末に保存されているマニフェストを読み込み、\_localManifestとしてパースします。
この際、キャッシュされたマニフェストがあればそれを読み込みバージョン比較を行い、バージョンが新しい方を_localManifestとします。
キャッシュされたマニフェストに関しては後ほど説明します。

### 2. バージョンマニフェストダウンロード

remoteVersionUrlに書かれたURLからバージョンmanifestファイルをダウンロードし、パースします。
_localManifestとバージョンが変わらない場合は終了となります。
_localManifestとバージョンが異なる場合、もしくはremoteVersionUrlにファイルがない場合は3.に進みます。

### 3. メインマニフェストダウンロード

remoteManifestUrlに書かれたURLからメインのmanifestファイルをダウンロードし、_remoteManifestとしてパースします。
_localManifestとバージョンが同じ場合は終了となります。
違う場合は、4.に進みます。

### 4. アセットの追加/更新/削除

_localManifestと_remoteManifestのassetsの内部を比較します。

<strong>追加</strong>：_localManifestになくて、_remoteManifestにはある
　(上記の例だとziptest.zip)

<strong>更新</strong>：_localManifestになくて、_remoteManifestにもあるがmd5値が変更されている
　(上記の例だとtest.png)

<strong>削除</strong>：_localManifestにあり、_remoteManifestにはない
　(上記の例だとtest3.png)

の要領で動作を行います。

最終的に、<strong>remoteManifestの定義通りにローカルのファイルが保存されている</strong>事になります。

### 5. unzip (必要であれば)

assetsのmd5キーと同じ位置に、compressedキーを追加するとそのファイルをzipとして解凍してくれます。
現在はzipにしか対応していないようです。
解凍されたファイルは[zip名]のフォルダ下に配置されます。

### 6. メインマニフェストのキャッシュ

全てのリソースをダウンロードし終わったら、_remoteManifestをキャッシュします。
次回以降1. の手順でこのキャッシュされたmanifestを読み込みます。

## まとめ

実際に使ってみるといくつか不便な点もあるのですが、非同期でリソースをダウンロード
する場合に必要な機能は最低限含まれているので使ってみてはいかがでしょうか。


誰かのお役に立てば。