AssetBundleの依存関係と更新判定

## 発生した問題事象

- マテリアルAの_MainTex (シェーダのプロパティ) にはテクスチャaを設定している
- テクスチャaのTextureTypeはSprite
- マテリアルA、テクスチャaをそれぞれAssetBundleにしている

上記の状態で、テクスチャaのFormatをRGBA32からASTC6x6にしても、MemoryProfilerで確認するとRGBA32の状態でメモリに乗っている



## 前提知識

原因調査の前に前提となる知識をまとめておく
参考になるURLも載せておくので、そちらを見るとより詳細に理解できると思う

### AssetBundleのインクリメンタルビルドと更新判定

AssetBundleをビルドすると、manifestファイルも生成される
manifestファイルにはAssetFileHashとTypeTreeHashという2つのハッシュ値が記載されていて、この値を使って更新があったアセットのみをビルドする仕組みがUnityには備わっている

manifestファイルを削除すると、アセットに変化がなくてもビルドされる (新規扱い)

＜参考＞
[【Unity】AssetBundleのインクリメンタルビルドの仕組みと挙動](https://light11.hatenadiary.com/entry/2019/12/13/223432)
[【Unity】AssetBundleの更新判定を行うAssetFileHashとTypeTreeHashについて](https://light11.hatenadiary.com/entry/2019/11/26/225813)

### AssetBundleの依存関係

あるアセットをAssetBundleにすると、そのアセットに必要なサブアセットも含めてビルドされる
例えば、プレハブXの中でテクスチャaとシェーダ1を使用している場合、プレハブXをビルドするとテクスチャa/シェーダ1も含んだAssetBundleが出来上がる
アセット同士の参照関係を意識せずに使えて便利な機能ではあるけれど、意識して使わないと下記のデメリットもある

1. ファイルサイズの肥大化
2. メモリ使用量の増加
3. 更新の影響範囲

もしプレハブXと同様にテクスチャaを使用するプレハブYがあった場合、プレハブXとプレハブYをそれぞれビルドすると

```
プレハブXのAssetBundle　[プレハブX, テクスチャa, シェーダ1]
プレハブYのAssetBundle　[プレハブY, テクスチャa]
```

このようにそれぞれのAssetBundleにテクスチャaが含まれるため、テクスチャaの分だけファイルサイズは肥大化する
そして元は同一のファイルであったとしても、個々のAssetBundleに複製される形で同梱されたため、プレハブXとプレハブYをロードするとテクスチャaは2枚分メモリに乗ってしまう
また、テクスチャaに更新があった場合、プレハブXとプレハブYもビルドし直す必要がある

これを回避するのがAssetBundleの**依存関係**
今回の例では、テクスチャaもAssetBundleにすることで下記のような関係性になる

```
テクスチャaのAssetBundle [テクスチャa]
プレハブXのAssetBundle　 [プレハブX, シェーダ1] 依存関係：テクスチャaのAssetBundle
プレハブYのAssetBundle 　[プレハブY]           依存関係：テクスチャaのAssetBundle
```

プレハブXやプレハブYをロードする際に、テクスチャaのAssetBundleからテクスチャaがロードされて、それぞれのプレハブはそれを参照する形で動作する
これによってファイルサイズの肥大化やメモリの無駄な消費を避けることができる
テクスチャaの更新時も、各prefabは参照しているだけなので、テクスチャaのみビルドしなおせば良い

＜参考＞
[【Unity】ファイルサイズとメモリ消費を無意味に増やさない為の、AssetBundleと依存関係についてのメモ](https://tsubakit1.hateblo.jp/entry/2016/03/26/024947)

## 原因調査

### Q1. AssetBundleの依存関係はどうなっているのか

> マテリアルAの_MainTexにはテクスチャaを設定している
> マテリアルA、テクスチャaをそれぞれAssetBundleにしている

という状態なので、依存関係としては下記のようになっていると考えられる

```
マテリアルAのAssetBundle [マテリアルA] 依存関係：テクスチャaのAssetBundle
テクスチャaのAssetBundle [テクスチャa]
```

### A1. 同梱＋依存関係

AssetBundleの中身の閲覧には[AssetBundleBrowser](https://docs.unity3d.com/ja/2019.4/Manual/AssetBundles-Browser.html)を用いた

テクスチャaのAssetBundleへの**依存関係があるのに、テクスチャaがマテリアルAのAssetBundleにも含まれている**

<img width="400" alt="AssetBundleInspector.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/bca32274-6aa8-c5f3-8f51-86e21dad5347.png">

こうなった原因としては

> テクスチャaのTextureTypeはSprite

が原因と考えられる
SpriteAtlasに関する記事だが[【Unity】AssetBundleでSpriteAtlasを使用する際に知らないと起こすかもしれないトラブルと、その回避方法](https://tsubakit1.hateblo.jp/entry/2018/12/24/210214)には下記のように書かれている

> 普通に使っている…つまりSpriteを経由してテクスチャにアクセスしてるだけの場合はデータサイズは増えません。AssetBundleに格納したTextureのデータは破棄され、Textureを取得してもNullを返されます。
> 一方普通ではない使い方…つまりSpriteを使いつつもTextureとしても使っている場合はデータサイズは増えます。例えばRawImageのPrefabで参照していたり、MaterialのMainTextureとかで参照している場合もそうでしょう。その場合はSpriteAtlasでパックした画像と、パック前の両方が含まれます。


TextureTypeをDefaultに設定してみると、依存関係のみでテクスチャaは同梱されていない

<img width="400" alt="AssetBundleInspector.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/20b5b6e2-26e7-17fe-e493-45ac0e573ad9.png">

### Q2. テクスチャaの更新時の挙動はどうなっているか

#### パターン1

マテリアルAのAssetBundleにテクスチャaも同梱されているので、マテリアルとテクスチャ両方のAssetBundleが更新される

#### パターン2

AssetBundle間に依存関係があるので、テクスチャaのAssetBundleしか更新されない

### A2. パターン2 - テクスチャaのAssetBundleのみ更新

対象のファイルしかないスモール構成で検証した

テクスチャaのFormatがRGBA32の状態で一度AssetBundleをビルドし、FormatをASTCに変えて再度ビルドしたところ、
**テクスチャaのAssetBundleだけ更新された**のがファイルの変更時間とUnityのログから確認できた

AssetBundleのファイルサイズもテクスチャaのほうはASTCに圧縮されることで小さくなったが、マテリアルAのほうは変わっていない

|テクスチャaのFormat |テクスチャaのAssetBundle|マテリアルAのAssetBundle|
|---|---|---|
|RGBA32| 556KB | 560KB |
|ASTC| 179KB | 560KB |

マテリアルAのmanifestファイルを削除して再度ビルドすると、マテリアルAのAssetBundleは183KBになり、テクスチャaの設定変更が反映されたことが確認できた

## まとめ

- TextureTypeがSpriteのテクスチャをマテリアルから参照して個々をAssetBundleにすると、依存関係が構築されつつもマテリアルのAssetBundleにテクスチャが同梱される
- インクリメンタルビルドの更新判定は、依存関係と同梱関係では依存関係が重視されるため、テクスチャ(被参照側)を更新しても、テクスチャのAssetBundleのみが再ビルドされる
- マテリアルは依存関係のあるAssetBundleのテクスチャではなく、同梱されているテクスチャを使用する

これらによって、テクスチャのFormatを変えて圧縮されたにも関わらず、古い状態で動作していた
解決策としては「同梱＋依存関係でも問題ないようにする」か「同梱か依存の片方or両方を解消する」が考えられる

**同梱＋依存関係でも問題ないようにする**

ビルド後にmanifestファイルを残さない、もしくはビルド前にmanifestファイルを削除することで、更新判定をせず強制的にAssetBundleをビルドすれば今回の事象は発生しない
AssetBundle化するアセット全てのmanifestを残さないか削除するのが最も簡単だが、アセット数が多ければ多いほどビルド時間が長くなってしまうのが欠点となる
プレハブやマテリアルなど他のアセットを参照するアセットに限定して削除したり、更新されたAssetBundleを参照するAssetBundleのmanifestを削除したりなど、対象を限定する仕組みを入れればビルド時間の肥大化は抑えることができる

**同梱か依存の片方or両方を解消する**

① TextureTypeをDefaultにする

A1で書いたように、同梱を解消する案
マテリアルに設定しているテクスチャをSpriteとして利用していなければ最も手軽な解決策になる

② マテリアルにテクスチャを設定しない

マテリアルからテクスチャへの参照を無くすことで同梱と依存関係の両方を解消する案
3DオブジェクトなどMeshRendererで使うマテリアルには使えないが、SpriteRendererやImageならばコンポーネント側にテクスチャを設定できるため、マテリアルにテクスチャを設定しないという手が使える


③ マテリアルとテクスチャをまとめて1つのAssetBundleにする

1つのAssetBundleにマテリアルとテクスチャの両方が含まれるように設定することで依存関係を解消する案
テクスチャ単体でAssetBundle化せず同梱されるがままにするという手もあるが、他にテクスチャを参照しているアセットがあった場合に重複が発生する