【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 アセットバンドル編

2年目のゲームプログラマがゲーム会社でUnityを使用して
スマートフォン向けのゲームを開発して感じたことをまとめておきます

* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 コーディング編](http://qiita.com/baba_s/items/f2ad850dd7fc84165e96)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 拡張メソッド編](http://qiita.com/baba_s/items/a0490ea36f6a5bcf8b07)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 デバッグメニュー編](http://qiita.com/baba_s/items/08cecb26e656900c3bb3)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 UI編](http://qiita.com/baba_s/items/c8d25456c0ee4ef52f94)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 アセットバンドル編](http://qiita.com/baba_s/items/a64b97de1b3cd41460f9)

# アセットバンドル

今回、初めてアセットバンドル周りの実装を行いました
まだ依存関係の仕組みなどがちゃんと理解できていないですが、
今後のためにやったことを書き残しておきます

## アセットバンドルの作成

### リソースのフォルダ管理

今回開発したアプリにおいて、リソースは大きく分けて次の2種類がありました

* アプリに内包するリソース
* アセットバンドルに逃がすリソース

開発中はすべてのリソースを`Assets/Resources`フォルダに格納していました

そして、内包するリソースは`Assets/Resources/LocalData`フォルダに格納しておき
JenkinsでAPKやIPAを作成するときに、`Assets/Resources`内の`LocalData`フォルダに入っていない
すべてのリソースを削除してからAPKやIPAの作成を開始するようにして
アセットバンドルに逃がすリソースがアプリに含まれないようにしました

![](https://dl.dropboxusercontent.com/u/52568393/assetbundle/0010.png)

### Switch Platformの時間の短縮

複数のプラットフォーム向けのアプリを開発している場合
各プラットフォーム用にアセットバンドルを作成する必要がありますが
1つのプロジェクトですべてのプラットフォーム用のアセットバンドルを作成しようとすると
Switch Platformに長時間かかってしまいます
<font color="red">今回開発したアプリではSwitch Platformに1時間半以上かかりました</font>

そこで、Switch Platformの時間を短縮するために下記の2つの方法を試しました

* Fast Platform Switchを使用する
* 複数のプラットフォーム用にプロジェクトをチェックアウトする

#### Fast Platform Switchを使用する

https://www.assetstore.unity3d.com/jp/#!/content/11134

Fast Platform Switchを使用すると2回目以降のプラットフォームの切り替えが高速になります

とても便利なアセットで、最初こちらを使用していたのですが
プラットフォームの切り替え中にUnityがクラッシュする不具合にときどき遭遇したので
途中からは複数のプラットフォーム用にプロジェクトをチェックアウトする方法をとりました

#### 複数のプラットフォーム用にプロジェクトをチェックアウトする

SVNからプロジェクトをチェックアウトしてきた後に
一度だけSwitch Platformしておくだけなのでオススメです

### シーンのアセットバンドル化

今回開発したアプリではUIの修正もサーバーのリソース更新のみで完結できるように
UIのプレハブもアセットバンドルに含めるようにしました

ただし、シーンファイルをそのままアセットバンドル化できなかったため
アセットバンドルを作る前に次のような準備を行うようにしました

1. シーンファイルのルートのオブジェクトをプレハブに変換する
2. プレハブをアセットバンドル化する
3. アセットバンドルからプレハブを読み込んで生成するためのシーンを用意する

## アセットバンドルの読み込み

### DL中はターゲットフレームレートを60に設定する

```cs
Application.targetFrameRate = 60;
```

今回開発したアプリのターゲットフレームレートは30だったので、アセットバンドルのDLも少し遅くなってしまいました
なので、アセットバンドルのDL中のみターゲットフレームレートを60に設定することでDL時間を短縮するようにしました

### `WWW.LoadFromCacheOrDownload`を複数同時に実行する

最初は`WWW.LoadFromCacheOrDownload`を1つしか実行しておらず、DLに時間がかかってしまっていたので
`WWW.LoadFromCacheOrDownload`を複数同時に実行することでDL時間を短縮しました

### アセットバンドルがキャッシュに存在するかどうか

アセットバンドルがキャッシュに存在するかどうかは`Caching.IsVersionCached`で確認できます

今回開発したアプリでは`Caching.IsVersionCached`を使用して
不要なアセットバンドルがキャッシュに残っていないかどうかを確認するようにしました

### アセットバンドルの個別削除

アセットバンドルを一括で削除する時は`Caching.CleanCache`を使用したのですが
個別で削除する時は`WWW.LoadFromCacheOrDownload`を使用しました

`WWW.LoadFromCacheOrDownload`の第3引数の`crc`に0以外の適当な値を渡すことで
アセットバンドルを個別で削除することが可能です

```cs
WWW.LoadFromCacheOrDownload( url, version, 【適当な値】);
```

`Caching`クラスに個別削除できそうな関数がいくつかあったのですが
これらを使用してもアセットバンドルを個別削除することはできませんでした
（使用を推奨しないことを表す`Obsolete`属性も適用されていました）

```cs
[Obsolete("this API is not for public use.")]
[WrapperlessIcall]
public static bool CleanNamedCache(string name);
[Obsolete("This function is obsolete and has no effect.")]
[WrapperlessIcall]
public static bool DeleteFromCache(string url);
```

なので、少し怖いやり方ですが今回は`WWW.LoadFromCacheOrDownload`の第3引数の`crc`に
0以外の適当な値を渡す方法で、アセットバンドルの個別削除を実現しました

#### 参考サイト様

* [UnityのAssetBundleダウンロード実装 - Qiita](http://qiita.com/otmb/items/a2fb228ccf21929d5be4)
* [キャッシュ済みのAssetBundleを個別に削除する方法 - パルテノン神殿](http://parthenontemple.tumblr.com/post/101248391343/assetbundle)

### キャッシュを削除するボタンを用意する

最近では「[白猫プロジェクト](http://colopl.co.jp/shironekoproject/)」や「[フルボッコヒーローズ](http://official.fullbokko.drecom.jp/)」、「[リトルテイルストーリー](http://www.cc2.co.jp/lts/)」などのゲームで
不要なキャッシュを削除するためのボタンがタイトル画面に配置されているのを見かけます

今回開発したアプリでもキャッシュを削除するためのボタンをゲーム内に用意することで
万が一、不要なアセットバンドルが端末に溜まってしまっても削除できるようにしました

### アセットバンドルのプラットフォーム間の互換性

アセットバンドルはプラットフォーム間で互換性があります
例えばAndroid用のアセットバンドルやiOS用のアセットバンドルをUnityエディタ上で使用することが可能です
そのため、Android、iOS向けのアプリを開発している場合は
Unityエディタ用のアセットバンドルを作成する必要はありません

#### 参考サイト様

* [Unity - マニュアル: アセットバンドル FAQ](http://docs.unity3d.com/ja/current/Manual/abfaq.html)

## 遭遇した問題

### 古いバージョンのアセットバンドルがキャッシュに溜まっていく

`WWW.LoadFromCacheOrDownload( url, version )`の`version`に新しい値を入れてアセットバンドルをDLすると
古いバージョンのアセットバンドルがキャッシュに残ったままになります（Unityの仕様のようです）

同じアセットバンドルの差分更新を行いたい場合は
アセットバンドルを作成する時に`BuildPipeline.BuildAssetBundle`で`crc`を受け取っておき
`WWW.LoadFromCacheOrDownload( url, 1, crc )`というような記述方法で
アセットバンドルをDLすることで実現可能のようです

この時、`version`に新しい値を入れてしまうと古いバージョンのアセットバンドルが
キャッシュに残ってしまうため、`version`には常に固定の値を指定します

```cs
int crc = 0;
BuildPipeline.BuildAssetBundle( mainAsset, assets, pathName, out crc );
```

```cs
var www = WWW.LoadFromCacheOrDownload( url, 1, crc );
```

### iOSの同時ファイルオープン可能数によるエラー

開発中、iOSにおいて256個以上のアセットバンドルをDLして展開したままにしてしまったことで
それ以上リソースを読み込めなくなり、シーン遷移できなくなる問題に遭遇しました
使用メモリの削減のためにもオンメモリにする必要がないアセットバンドルは
`WWW.LoadFromCacheOrDownload`でDLした後に`AssetBundle.Unload`で解放する必要があります

### フォルダのロックによるエラー

```cs
UnauthorizedAccessException: Access to the path "XXXX" is denied.
```
```cs
IOException: Lock violation on path XXXX
```
```cs
Couldn't move cache data into place.
```

アセットバンドル検証中にアセットバンドルのDL先のフォルダにロックがかかってしまうことがありました
フォルダにロックがかかった状態で`WWW.LoadFromCacheOrDownload`を実行したところ
端末によって上記のようなエラーが発生することを確認したので書き残しておきます

### スクリプトの変更によるエラー

```
AssetBundle loading failed because the AAAA script serialization hash does not match. 
Supported: BBBB, loading: CCCC
The asset bundle 'DDDD' could not be loaded because it references scripts that are not compatible with the currently loaded ones. 
Rebuild the AssetBundle to fix this error.
```

スクリプトを関連付けているプレハブをアセットバンドル化した場合は
それ以降、そのスクリプトに`public`変数や`SerializeField`属性が適用された`private`変数を追加すると
上記のエラーが発生してアセットバンドルの読み込みに失敗するようになります

```cs
public class Player : MonoBehaviour
{
}
```

例えば、上記のスクリプトをアセットバンドル化するプレハブに関連付けていた場合

```cs
public class Player : MonoBehaviour
{
	public int Level;
	
	[SerializeField]
	private string Name;
}
```

アセットバンドルを作成した後にスクリプトをこのように変更してからアセットバンドルをDLすると
スクリプトの関連付けに失敗して前述のエラーが出力されます

このエラーを回避するためには下記のような対応を行う必要があります

* スクリプトを変更した段階でアセットバンドルを作りなおす
* アセットバンドル作成後にスクリプトに`public`変数や`SerializeField`属性が適用された`private`変数を追加しない

#### 参考サイト様

* [Unity の AssetBundle は何がクソなのか - Qiita](http://qiita.com/melpon/items/1290eea370802de5c2b6)
* [Asset Bundle mismatch keeps returning. - Unity Answers](http://answers.unity3d.com/questions/555469/asset-bundle-mismatch-keeps-returning.html)

### 原因不明のエラー

```
WWWCached data can only be accessed using the assetBundle property!
```
```
You are trying to load data from a www stream which had the following error when downloading.
```

一度だけUnityエディタ上で発生したエラーです

## その他

### Windows7におけるアセットバンドルのDL先

```cs
C:\Users\【ユーザー名】\AppData\LocalLow\Unity\WebPlayer\Cache\【Company Name】
```

## 参考サイト様

* [Unity - Developer's voice - Lord of Knights](http://japan.unity3d.com/gallery/made-with-unity/voices/lord-of-knights/)
* [はじめてのUnityゲーム開発でつまずきやすい3つのポイント — Mobage Developers Blog](http://developers.mobage.jp/blog/point-that-it-is-easy-to-stumble)
* [いまさら書けないAssetBundleのcache周りの知見](http://sassembla.github.io/Public/2015:02:04%2012-47-46/2015:02:04%2012-47-46.html)
* [AssetBundleを高速に作る](http://sassembla.github.io/Public/2014:12:15%2000-00-00/2014:12:15%2000-00-00.html)