Unity
AssetBundle

AssetBundleのVariantsを完全に理解する

はじめに

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

想定する読者層

この記事でわかること

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

AssetBundle Variantsとは

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

何故Variantsが必要なのか

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

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

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

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

Variantsの使い方

Inspector上から設定するやり方については以下の記事にわかりやすくまとまっている。
【Unity】AssetBundleのVariantsでロードするアセットを差し替える - テラシュールブログ

ここでは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

1. アセットを用意

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

image.png
image.png
image.png

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

image.png

2. ビルドする

大体こんな感じ。

  • variantにするアセットには同じassetBundleNameを指定する
  • assetBundleVariantに任意の文字列を入れる
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名が入っていることがわかる。

補足

ファイル名を同じにする必要がある、という事はBuildAssetBundleOptions.DisableLoadAssetByFileName(とWithExtention)とか指定したら動かなくなるのかな?と思って試した所、問題なく動いた。
また、addressableNamesを同じにしても動かなかった。ファイル名は絶対に同じにしないといけない模様。
どういう感じに解決しているのか気になる所…

3. ロードする

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

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】そろそろ楽がしたい!新アセットバンドルワークフロー&リソースマネージャー詳細解説 P59

その前の講演でもVariantsは微妙!みたいな事が言われており、Unity公式としてはAssetBundle Variantsは今後非推奨という雰囲気が漂っている。

代替手段について

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

まとめ

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

参考資料