Addressable Asset Systemをそろそろ理解したいのだが、その前にAssetBundleのベストプラクティスを考える前提知識を整える必要がある。
AssetBundleにはいくつか利用方法(読み込み方法)があるようだが、それぞれ特徴があるようなので確かめてみる。
AssetBundleについての基本知識
Assetを読み込む方法
LoadFromFile
AssetBundleファイルのパスを指定して読み込む。
AssetBundle assetBundle = AssetBundle.LoadFromFile(assetBundlePath);
LoadFromStream
AssetBundleファイルのパスを指定して、Streamで読み込む。
AssetBundle assetBundle; using (FileStream fileStream = new FileStream(assetBundlePath, FileMode.Open, FileAccess.Read)) { assetBundle = AssetBundle.LoadFromStream(fileStream); }
暗号化されている場合、
FileStream fileStream = new FileStream(assetBundlePath); // なんらかの復号Stream DecryptStream decryptStream = new DecryptStream(fileStream, password, salt); AssetBundle assetBundle = AssetBundle.LoadFromStream(decryptStream);
LoadFromStreamの復号について
暗号化アルゴリズムによって中のメモリ確保が変わる。 一般的な暗号化Stream(CryptStreamなど)を用いた場合は、上記の処理でAssetBundleファイルそのままをアンマネージドメモリへ読み込む。と思われる。(未検証) CanSeekがtrueな暗号化Streamを用いた場合は、上記処理ではメタ情報のみがMonoメモリに読み込まれる。 また、LoadAssetする時点で復号に用いたStreamが破棄されている場合、Loadに失敗する。 詳細な実装はC++領域なため公開されていないが、この動作から、LoadAssetの段階でも、目的のAssetの部分までSeekし、目的のAssetだけを復号して読み込んでいると推測できる。
LoadFromMemory
Monoメモリ上のバイト列からAssetBundleを生成する。
byte[] bytes = File.ReadAllBytes(assetBundlePath);
AssetBundle assetBundle = AssetBundle.LoadFromMemory(bytes);
のように使用する。 AssetBundleが暗号化されていれば復号処理を挟む。
// ファイルから直接バイナリとして全て読み込む。 byte[] encryptedBytes = File.ReadAllBytes(assetBundlePath); byte[] decryptedBytes = Decryptor.Decrypt(encryptedBytes) AssetBundle assetBundle = AssetBundle.LoadFromMemory(decryptedBytes);
検証内容
準備
適当な音楽ファイルを20個用意した。全てmp3で総容量は98.6MB、平均して1ファイル5MBだった。 これをLZ4圧縮で1つのAssetBundleにビルドした。(平文AssetBundle) さらに、その上でCanSeekな暗号化Streamで暗号化したものを用意した。(暗号文AssetBundle) 具体的には、こちらの記事をそのまま流用させてもらった。 tsubakit1.hateblo.jp 平文AssetBundle、暗号文AssetBundle共にサイズは85.2MBだった。
検証
LoadFromFile, LoadFromStream, LoadFromMemoryを用いた3つの検証を行った。 それぞれ、AssetBundleを読み込む段階と、その後LoadAssetでAudioClipを1つ読み込む段階でメモリの使用状況を確認し、UsedMemoryの変化を記録した。 LoadFromStreamのみ暗号文AssetBundleを使用し、他は平文AssetBundleを使用している。 LoadFromMemoryはReadAllBytesが事前に必要なため、そこも確認している。
結果
LoadFromFile | LoadFromStream | LoadFromMemory | |
---|---|---|---|
ReadAllBytes | Mono: 7MB -> 88MB Unity: 110MB -> 110MB |
||
AssetBundle | Mono: 7MB -> 7MB Unity: 110MB -> 110MB |
Mono: 7MB -> 7MB Unity: 110MB -> 110MB |
Mono: 88MB -> 88MB Unity: 110MB -> 191MB |
AudioClip | Mono: 7MB -> 7MB Unity: 110MB -> 116MB |
Mono: 7MB -> 7MB Unity: 110MB -> 116MB |
Mono: 88MB -> 88MB Unity: 191MB -> 197MB |
ここから、以下の特徴が推測できる。
LoadFromFile
- これで取得するのはAssetBundleのメタ情報のみ。
- ここから
AssetBundle.LoadAsset<T>
で中のAssetを取得する際も、目的以外のAssetは無視され、不要なメモリは確保せず、メモリに優しい。
- ここから
- 暗号化されたAssetBundleファイルに使用できない。
LoadFromStream(CanSeekな暗号化を行った暗号文AssetBundle)
- これで取得するのはAssetBundleのメタ情報のみ。
- ここから
AssetBundle.LoadAsset<T>
で中のAssetを取得する際も、目的以外のAssetは無視され、不要なメモリは確保せず、メモリに優しい。
- ここから
- 暗号化されたAssetBundleファイルに使用できるが、そのための暗号化アルゴリズムに制約がある。
LoadFromMemory
- AssetBundleファイルを一括でMonoメモリに読み込む必要があり、その上でUnityメモリにコピーする必要もある。
- メモリ効率が非常に悪い。AssetBundle内の1つのAssetを使用したい場合でもAssetBundle全体を読み込む必要がある上に、適切に処理しても一時的にAssetBundle1つ分のメモリがMonoメモリとUnityメモリに同時に存在する状態を避けられない。
- 暗号化されたAssetBundleファイルに使用できる。
結論
平文のAssetBundleを使用する場合はLoadFromFileが適切。 暗号化されたAssetBundleは基本的にはLoadFromMemoryを使用するが、CanSeekな暗号化アルゴリズムを使用すればLoadFromStreamでメモリに優しいAsset読み込みが可能になる。