AssetBundleのVariantsを完全に理解する

# はじめに

AssetBundleにはVariantsという機能があります。が、これについてあまりにもネット上での解説や使用事例を見かけなかったので、自分で改めて調べてまとめようと思った次第です。

## 想定する読者層

- AssetBundleを理解している人
  - 理解していない人はこちら → [AssetBundleを完全に理解する \- Qiita](https://qiita.com/k7a/items/d27640ac0276214fc850)
- AssetBundleのVariantsを理解したい人

## この記事でわかること

- Variantsの使い方、用途
- Variantsの仕組み
- Addressable Asset SystemにおけるVariantsの実現方法

# AssetBundle Variantsとは

AssetBundle Variantsは、同じアセットに対して「日本語版・英語版」や「HD版・SD版」など複数の種類を用意したい場合に使う機能。

<img src="https://qiita-image-store.s3.amazonaws.com/0/97014/c37fa2a2-2411-ec73-17e1-a703623e0a5b.png" width=400>

## 何故Variantsが必要なのか

上記の例だけだと、「それぞれ別のAssetBundleとして作成して、ロードするAssetBundleを切り替えればいいだけなのでは？」となる。
例えばAssetBundle名を`character/texture_sd`と`character/texture_hd`にして、設定によってどちらかからロードすれば良い。
が、Variantsが本当に役に立つのは**AssetBundle同士に依存関係がある場合**。
例えば、キャラクターのテクスチャを参照したprefabを作ることを考える。prefabにはAnimatorとか何らかのコンポーネントが付いてるイメージ。

<img src="https://qiita-image-store.s3.amazonaws.com/0/97014/090d8335-9335-4b04-7f42-ac25d3ea1b24.png" width=200>

この時、当然「HD版のテクスチャを使うprefab」と「SD版のテクスチャを使うprefab」が必要になる。
その為、参照するテクスチャを変えただけのprefabを用意することになる。

<img src="https://qiita-image-store.s3.amazonaws.com/0/97014/b0956f55-ad12-df0c-6161-c746cee1d823.png" width=400>

キャラクターのテクスチャを差し替えたいだけなのに、prefabをまるごと複製しないといけないのは冗長で気持ちが悪い。
複製することによって具体的に発生する問題としては、仮にprefabを修正したい場合、**修正を両方のprefabに反映しないといけない**ため、気を付けないと予期せぬ不具合に繋がる可能性がある。
HD/SDならまだ2種類のためそこまで手間は掛から無いかもしれないが、これが例えば何十カ国語に対応！みたいな話になると相当しんどい事が予想される。

そこでVariantsを使うことで、prefabを複数種類用意せずにテクスチャを切り替えることが可能になる。

<img src="https://qiita-image-store.s3.amazonaws.com/0/97014/79f6bb65-c09d-106c-aa66-0c844e9393b7.png" width=600>

# Variantsの使い方

Inspector上から設定するやり方については以下の記事にわかりやすくまとまっている。
→ [【Unity】AssetBundleのVariantsでロードするアセットを差し替える \- テラシュールブログ](http://tsubakit1.hateblo.jp/entry/2016/04/25/002613)

ここではAssetBundleBuildの配列を組んでビルドする場合にVariants化するやり方を解説する。

※ 2018.3以前ではAssetBundleBuildを指定してビルドする場合にvariantsがちゃんと動かないバグがあった模様。筆者は2018.2.4f1にて特に問題ない事を確認。
参考：[Unity Issue Tracker - [Asset Bundle] Variant bundles built via a build map show an error and have pink texture](https://issuetracker.unity3d.com/issues/asset-bundle-variant-bundles-built-via-a-build-map-show-an-error-and-have-pink-texture)

## 1. アセットを用意

hd/texture.png、sd/texture.pngを用意し、hd/texture.pngを参照するprefabを作成する。
ここでprefabから参照するのは、variants化したいアセットのどれでも構わない。（今回の場合はhd/texture.png、sd/texture.pngのどちらでもOK）
注意点としては、**variants化したいアセットは全て同じファイル名にする必要がある**。
そのため、必然的に別のフォルダに配置する必要がある。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/97014/1a6436b3-b492-9d6c-ac43-18e7ed795461.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/97014/b662cdee-fd11-2502-e1e1-1197d4a4ca66.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/97014/e1fa58bc-1a0b-4b4d-b231-a36df4fd1946.png)


Prefabのコンポーネントはこんな感じ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/97014/fb7a3963-76ff-f02a-e2b2-53d519169b3d.png)


## 2. ビルドする

大体こんな感じ。

- variantにするアセットには同じassetBundleNameを指定する
- assetBundleVariantに任意の文字列を入れる

```cs
using System.IO;
using UnityEditor;

public static class BuildTest
{
    [MenuItem("Test/VariantsBuild")]
    public static void Build()
    {
        var prefab = new AssetBundleBuild();
        prefab.assetNames = new string[1] { "Assets/AssetBundleResources/variant/prefab.prefab" };
        prefab.assetBundleName = "prefab";

        // variantsにするアセットのAssetBundle名は同じにする必要がある
        var textureAssetBundleName = "texture";

        var hd = new AssetBundleBuild();
        hd.assetNames = new string[1] { "Assets/AssetBundleResources/variant/hd/texture.png" };
        hd.assetBundleName = textureAssetBundleName;
        hd.assetBundleVariant = "hd";

        var sd = new AssetBundleBuild();
        sd.assetNames = new string[1] { "Assets/AssetBundleResources/variant/sd/texture.png" };
        sd.assetBundleName = textureAssetBundleName;
        sd.assetBundleVariant = "sd";

        var builds = new AssetBundleBuild[3] { prefab, hd, sd };

        var targetDir = "VariantsTest";

        if (!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir);

        BuildPipeline.BuildAssetBundles(targetDir, builds, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneOSX);
    }
}
```

これで以下の成果物ができる。variantが設定された場合、書き出されるファイル名は`{AssetBundle名}.{Variant名}`になる。

- prefab
- prefab.manifest
- texture.sd
- texture.sd.manifest
- texture.hd
- texture.hd.manifest
- VariantsTest（AssetBundleManifest）
- VariantsTest.manifest

AssetBundleBrowserを用いてprefabのメタ情報を覗いてみると、DependenciesにはAssetBundle名が入っていることがわかる。

<img src="https://qiita-image-store.s3.amazonaws.com/0/97014/30774c50-af04-8b20-1786-df44ad3fcb02.png" width=600>

### 補足

ファイル名を同じにする必要がある、という事は[BuildAssetBundleOptions\.DisableLoadAssetByFileName](https://docs.unity3d.com/ja/2017.4/ScriptReference/BuildAssetBundleOptions.DisableLoadAssetByFileName.html)（と[WithExtention](https://docs.unity3d.com/ja/2017.4/ScriptReference/BuildAssetBundleOptions.DisableLoadAssetByFileNameWithExtension.html)）とか指定したら動かなくなるのかな？と思って試した所、問題なく動いた。
また、[addressableNames](https://qiita.com/k7a/items/afe80c21b8c77cbd4eb5)を同じにしても動かなかった。ファイル名は絶対に同じにしないといけない模様。
どういう感じに解決しているのか気になる所…

## 3. ロードする

こんな感じのスクリプトをMonobehaviourのStartなんかに貼っ付ける。

```cs
var variant = "hd"; // ここを切り替えるとHD/SDで切り替えができる

// prefabのAssetBundleをロード
var prefabAB = AssetBundle.LoadFromFile("VariantsTest/prefab");
// テクスチャのAssetBundleのどちらかをロード
var textureAB = AssetBundle.LoadFromFile("VariantsTest/texture." + variant);

// prefabをロードし、Instantiateする
var prefab = prefabAB.LoadAsset<GameObject>("Assets/AssetBundleResources/variant/prefab.prefab");
var parent = GameObject.Find("Canvas").transform;
Instantiate(prefab, parent);

// AssetBundleをロードした方のテクスチャが使われる
```

要はLoadAsset時にHD版のAssetBundleがロードされていたらHD版のSprite、SD版のAssetBundleがロードされていたらSD版のSpriteが読まれる。
また、**同じAssetBundle名が指定されたAssetBundleは同時にはロードできない**ため、例えばHD版のAssetBundleをロードした後にSD版のAssetBundleをロードしようとするとエラーになる。

# Addressable Asset SystemにおけるVariantsについて

ふ〜ん、Variants便利そうじゃん！と思いきや、実は**Addressable Asset SystemはVariants非対応**。

参考：[【Unite 2018 Tokyo】そろそろ楽がしたい！新アセットバンドルワークフロー&リソースマネージャー詳細解説](https://www.slideshare.net/UnityTechnologiesJapan/unite-2018-tokyo-96499382/UnityTechnologiesJapan/unite-2018-tokyo-96499382) P59
<img src="https://qiita-image-store.s3.amazonaws.com/0/97014/03045db7-9bb0-1adc-63d6-f74b1fcaa179.png" width=600>

[その前の講演](https://www.slideshare.net/Unite2017Tokyo/unite-2017-tokyo-75808763)でもVariantsは微妙！みたいな事が言われており、**Unity公式としてはAssetBundle Variantsは今後非推奨**という雰囲気が漂っている。
<img src="https://qiita-image-store.s3.amazonaws.com/0/97014/b3a7e8a0-1a4d-16d7-e0a1-4489d2d2e748.png" width=600>

## 代替手段について

じゃあAAS使う場合はVariantsはどうすれば良いの？という話は、恐らく**複数種類のprefabを作って**、prefabに対応するアドレスの末尾にHDとかSDとか付けて、アドレス解決時によしなにしろ、という事だと思われる。
でもprefab複製したら諸々面倒…という冒頭での話は、恐らく2018.3から入る[Prefab Variants](https://docs.unity3d.com/2018.3/Documentation/Manual/PrefabVariants.html)で解決してね、という事になりそう。
PrefabVariantsはまだ触ったことが無いので詳細については知らないが、恐らくベースとなるprefabからテクスチャだけ変えた派生prefabを作成し、ベースのprefabを修正すれば派生のprefabも修正される、という感じで、複製することによる管理上の問題点については解決されると思われる。

# まとめ

- AssetBundle Variantsを使うことで、prefabが参照するアセットを差し替えることができる
- Addressable Asset Systemでは非対応
- Unity公式としては今後はPrefab Variantsを推奨していくような雰囲気がある

# 参考資料

- [【Unity】AssetBundleのVariantsでロードするアセットを差し替える \- テラシュールブログ](http://tsubakit1.hateblo.jp/entry/2016/04/25/002613)
- [【Unity】AssetBundleのVariantでローカライズ \- テラシュールブログ](http://tsubakit1.hateblo.jp/entry/2016/06/17/073000)
- [【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ～アセットバンドルの未来と開発ロードマップ](https://www.slideshare.net/Unite2017Tokyo/unite-2017-tokyo-75808763)
- [【Unite 2018 Tokyo】そろそろ楽がしたい！新アセットバンドルワークフロー&リソースマネージャー詳細解説](https://www.slideshare.net/UnityTechnologiesJapan/unite-2018-tokyo-96499382/UnityTechnologiesJapan/unite-2018-tokyo-96499382)
- [Unity \- Manual: Prefab Variants](https://docs.unity3d.com/2018.3/Documentation/Manual/PrefabVariants.html)