あんスタ！！Musicの3DライブにおけるAssetBundle戦略

# はじめに

この記事はHappy Elements株式会社 カカリアスタジオ Advent Calendar 2020 の 18日目 の記事です。

この記事では「あんさんぶるスターズ！！Music」の3DライブにおけるAssetBundle戦略について解説します。

あんスタMusicの一番の中心機能とも言える3Dライブ機能ですが、3Dライブ機能の裏側には膨大な数のアセットが存在しています。
それらのアセットをどのような形でビルドしてクライアントに提供するかで、運用の効率やクライアントの動作パフォーマンスなどに大きな影響を及ぼします。
今回は3DライブのAssetBundleの仕組みを作るにあたって工夫した点などを紹介していきたいと思います。

# 前提知識

Unityなのでアセットは当然AssetBundleにして配信する訳ですが、まずはAssetBundleの特性について軽く復習しておきます。

## AssetBundleは1つのファイルに複数のアセットを含むことができる

AssetBundleは1つのファイルに複数のアセットを含むことができます。
これは特に説明の必要も無いですが、後述のように普段はビルド時に勝手に必要なアセットが含まれるようになっているので、あまり意識する事が無かったりします。

## ビルド時に明示しない場合、AssetBundleにはそのアセットに必要な他のアセット全てが自動的に含まれる

主にSceneやprefabをAssetBundle化する時の話で、例えばprefab内で使われているテクスチャやシェーダーなどは基本的に**全て勝手に一緒に含まれる**ようになっています。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/d402c6cf-dabc-431b-f7f9-04105340cd7a.png' width="600" data-meta='{"width":1808,"height":1128}'>

勝手に含まれるのであまり意識することは無いですが、ここで気をつけなければ**パフォーマンスに顕著な影響を及ぼしかねない**問題があります。

### 複数のAssetBundleに同じアセットが含まれている場合、同時にロードした場合にメモリ上に2重にロードされてしまう

例えばprefabAとprefabBがありそれぞれで同じテクスチャが使われていた場合に、何も考えずにそれぞれAssetBundle化すると、それぞれのAssetBundleに同じシェーダーが含まれるようになってしまいます。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/78dbd526-33c0-51ea-bc41-03fe38b18c2c.png' width="700" data-meta='{"width":2696,"height":1122}'>

さらにその両方のAssetBundleをロードすると、同じシェーダーがメモリ上に2個存在するという状態になります。これは**パフォーマンス的にとても非効率**な状態です。
これを回避するには、後述するAssetBundle同士の依存関係を用いる必要があります。

## AssetBundleは複数のファイル間で依存関係を持つことができる

AssetBundleは、ビルド時に他のアセットから参照されているアセットを単体でビルドすると、依存関係を構築した状態でビルドされるという特性があります。

先程の例では、prefabAとprefabBをビルド対象としてAssetBundleをビルドした結果、共通シェーダーがそれぞれのAssetBundleに含まれてしまうという問題が発生していました。

これを防ぐには、共通シェーダーを別のAssetBundleとしてビルド対象に指定し、それぞれのprefabのAssetBundleから依存関係を持たせる必要があります。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/91b5902d-15fe-ebf3-9e45-af63039a7439.png' width="700" data-meta='{"width":2720,"height":1412}'>

ビルド時に共通化したいアセットをビルド対象として指定するようにすれば、AssetBundle間の依存関係の構築はUnityが自動で行ってくれます。
ただし、AssetBundle間に依存関係がある場合、勿論ですがロード時には依存するAssetBundleも一緒に読み込まなければいけません。

# AssetBundle戦略

## 適切な単位でまとめてAssetBundle化する

以上の特性を踏まえて、AssetBundleを構築するにあたっては、**適切な単位でまとめてAssetBundle化する**ということが重要になります。

ここで言う適切な単位というのは様々な意味合いがありますが、運用のしやすさやパフォーマンス面など総合的に加味して、必要十分な単位で分割したりまとめたりしてAssetBundle化するという事です。

具体的に言うと、キャラクターの3Dモデルにはポリゴン情報やテクスチャ、シェーダーなどが含まれますが、それらを全て個別に分割してAssetBundle化するのはあまり意味が無く、運用上でも不便です。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/d16c0164-5462-5ef5-5e4d-faf0e2e2e473.png' width="600" data-meta='{"width":1376,"height":974}'>

しかし、極端な話ですが全てのキャラクターの3Dモデルを1つのAssetBundleに詰め込むのは明らかにやり過ぎです。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/fb9628e3-e909-eef3-f887-460769d68103.png' width="600" data-meta='{"width":1438,"height":1076}'>

ではどのような判断基準で分割する単位を決めていけばよいのか？という事を解説していきます。

## 個別で利用する時の最小単位でまとめる

順当に考えれば、アセットを個別で利用する単位でまとめてAssetBundle化するのが直感的です。例えばキャラクターは各衣装ごと、ライブステージは各ステージごと、モーションや演出は各曲ごとといった様に、個別で利用する時の最小単位にまとめれば、運用上はほぼ問題無いでしょう。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/921d806f-4a58-5d37-3248-594f27b97ed2.png' width="600" data-meta='{"width":1520,"height":1258}'>

## アセットの容量と変更頻度に着目して分割する

しかし、ここでもう一つ気を付けたいのが、**まとめられたAssetBundleは変更がある度にまるごとDLし直す必要がある**という事です。
例えば複数のアセットをまとめた100MB超のAssetBundleがあった時に、その内の一つのアセットがほんの少しだけ変更されるだけで、ユーザーはその100MB超のAssetBundleをDLし直すことになってしまいます。

これではユーザー側に不便を強いることになり、また気軽に修正を加える事もできなくなってしまい運用面でも支障をきたすことになります。

この問題を解決するには、**AssetBundleを容量と変更頻度に着目して分割する**ことが必要になります。

例えば、容量は少ないが変更頻度の多いアセットAと、容量は大きいが変更頻度の少ないアセットBが同じAssetBundleに含まれていた場合、アセットAの変更の度に余分なアセットBの分までDLしなければいけないことになります。そのため、これらのアセットは分割してAssetBundle化したほうが良いと言えます。

あんスタMusicの場合、楽曲ごとのデータの容量の大部分を占めるのは**モーションデータ**です。しかし、モーションデータの変更頻度は比較的少なく、一方で容量の少ない演出データは変更頻度が高いです。

演出データはおおよそ数百KBと比較的軽量ですが、微調整することが多いです。また、スクリプトの変更によって演出に変更が無くてもシーンのAssetBundleには変更が加わる可能性があるため、変更頻度が高めです。

一方、モーションデータは人数によって20MB〜70MB程度とかなり大きいサイズとなります。しかし、変更頻度はシーンデータに比べて少ないです。

そのため、これらを分割してAssetBundle化することによって、重たいモーションデータを極力再度DLさせないようにしています。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/700de7ba-2003-f44f-99b4-f6e2f9d0c172.png' width="800" data-meta='{"width":2778,"height":1218}'>

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/3a33b026-e837-b181-2173-d9187f2036bc.png' width="800" data-meta='{"width":2092,"height":1224}'>

（モーションデータに変更があった場合はどうしようもないので、重いデータを再DLしてもらうことになります）

## 複数箇所で共通して参照されているアセットを単体でAssetBundle化する

前述の通り、複数箇所で同じアセットが使われている場合は共通化しなければパフォーマンス的に無駄が出ます。
特に3Dライブでは同じシェーダーが多くの場所で使われています。これを別のAssetBundleに分割していないとかなりの無駄が出てしまうため、シェーダーの共通AssetBundle化は必須となります。

<img title='image.png' alt='image' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97014/1e45f048-6e89-5c79-c582-049f8535bbc5.png' width="800" data-meta='{"width":2470,"height":1150}'>

# スクリプトによる分割ルールの制御

上記のようにAssetBundleの分割ルールを制御するには、手作業でのビルドはかなり不都合です。そのため、AssetBundleの分割ルールを自動的に設定できるような仕組みがあるとより良いです。
あんスタMusicでは[昨年のアドベントカレンダー記事](https://cacalia.kibe.la/shared/entries/66b70efe-dc06-4568-8d36-37575bc380cc)で紹介したAddressablesをベースとした社内独自のビルドシステムの上に、スクリプトで自動的にビルド設定ファイルを生成する仕組みを作ることで、AssetBundleのビルドルールを柔軟に制御できるようにしました。

# まとめ

- AssetBundle戦略は分割の単位が肝である
- 運用面やパフォーマンスを考慮して適切な単位で分割する必要がある
  - 容量と変更頻度に着目して分割する
  - 複数箇所で共通で利用されているアセットは個別にAssetBundle化する
- 分割ルールはスクリプトなどで自動的に設定できるようにしておく

AssetBundleの分割は運用・パフォーマンス面双方に影響を及ぼすため、AssetBundleの特性をしっかりと理解しておく事が大事だというお話でした。


# メンバー募集

Happy Elements株式会社 カカリアスタジオでは、
いっしょに【熱狂的に愛されるコンテンツ】をつくっていただけるメンバーを大募集中です！

もし弊社にご興味持っていただけましたら、是非一度
下記採用サイトをご覧ください。
[Happy Elements株式会社 採用特設サイト](https://recruit.happyelements.co.jp/)