unityでいってみよう!

unityがチョットワカル位の人のブログ

SpriteAtlasとAssetBundleについて検証してみよう!

概要

Unityでは複数のTextureを1枚のTexture(Atlas)に纏めることで、ドローコールを減らし描画パフォーマンスを向上させる効果が期待できます。 このAtlasを作成する為の機能としてLegacy Sprite PackerとUnity2017で追加されたSprite Packerの2種類が存在します。 この2種類の機能は排他的に使用する事が可能でProject Settings > Editor > Sprite Packer > Modeからどちらを使用するか選択することになります。

f:id:kimukats:20210714114127p:plain

ここでは、新旧SpritePackerとAssetBundle化について検証していきますが、ちょっと長くなってしまったので、検証した結果のポイントを先に述べますが、SpriteAtlasはSpriteAtalsを利用しているAssetの依存関係の要因とならないという点に注意して下さい。つまりSpriteAtlasに変更があっても、それを参照しているAssetのAssetBundleは更新の対象となりません。

検証環境

Unity2019.4.28f1

Legacy Sprite Packer

SpriteAtlasを作成するには、TextureImporterでTexture TypeSprite(2D and UI)に設定し、Sprite ModePacking TagにAtlas名を指定します。同じAtlas名を選択したSpriteが同じSpriteAtlasに纏められるという訳です。 f:id:kimukats:20210714134614p:plain

今回0~9とAのTextureを用意し、Packing TagNumbersというTagを設定しました。 SpritePackerWindowで表示するとこのように1枚のAtlasにまとまっていることがわかります。 f:id:kimukats:20210714134118p:plain

さてここで気になる点が一つ。 Sprite Packer Windowで表示されているAtlasは何処に生成されるのでしょうか? 実はLegacy Sprite Packerで生成されたSpriteAtlasはAssetではありません。 キャッシュとしてProject\Assets以下ではなくProject\Library\AtlasCache以下に生成されます。 f:id:kimukats:20210714135450p:plain

Legacy Sprite Packerで生成されたSpriteAtlasはAssetではないというのは非常に重要で、これはLegacy Sprite Packerで生成されたSpriteAtlasが単体ではAssetBundleを作成出来ないことを意味します。

では、SpriteAtlasをAssetBundle化する為にはどうすればよいでしょうか? これは、SpriteAtlasを参照するAssetをAssetBundle化することで実現出来ます。

図のようにSpriteAtlasに含まれる0のTextureを参照するAssetを生成してAssetBundle化を行います。 f:id:kimukats:20210714142153p:plain

尚、AssetBundleを生成するScriptはこんな感じです。

using UnityEditor;

public class AssetBundleBuild
{       
    [MenuItem("Assets/AssetBundle/Build")]
    public static void Build()
    {
    UnityEditor.BuildPipeline.BuildAssetBundles("Assets/AssetBundles", UnityEditor.BuildAssetBundleOptions.ChunkBasedCompression, UnityEditor.BuildTarget.Android);
    }    
}

出来上がったAssetBundleの中身を確認するとこんな感じです。

f:id:kimukats:20210714142810p:plain

サムネイルが小さくてわかりずらいですが、AssetBundleの2番目の要素としてSpriteAtlasが含まれていることが見て取れます。

ちなみに、AssetBundleの中身の表示はテラシュールブログで紹介されているものを使用させて頂いています。

同様にAというTextureを参照するSpriteAを作成し、alphabetabという名前で別のAssetBundleを作成してみます。

f:id:kimukats:20210714144129p:plain

こちらのAssetBundleにも同じSpriteAtlasが含まれていることがわかります。 f:id:kimukats:20210714144749p:plain

これは、SpriteAtlasを単独でAssetBundle化出来ない為、複数のAssetBundleにSpriteAtlasが重複して含まれしまうことを意味しますのでAssetBundleの構成には注意が必要です。alphabetabではTextureAのみ含まれていれば良いのでそれ以外のTextureは無駄でしかありません。TextureAのPacking TagをNumbersからAlphabetに変更してみます。

f:id:kimukats:20210714145808p:plain

SpriteAtlasはNumbersとAlphabetの2枚となりそれぞれ次のようになります。

f:id:kimukats:20210714145838p:plain f:id:kimukats:20210714145857p:plain

AがNumbersからAlphabetに移動していることが確認出来ます。 それではAssetBundleのビルドを行い、AssetBundleの中身を確認してみます。

f:id:kimukats:20210714151713p:plain f:id:kimukats:20210714151743p:plain

alphabetabに含まれるSpriteAtlasは変わっていますが、numbersabは以前のままです。何故でしょうか? ・・・ ・・・ 理由は、SpriteAtlasがAssetBundle化されるSpriteの依存ファイルとならない為です。 Sprite.prefabが参照しているつまり、依存しているAssetは、Texture0・Sprite-Default Material・Sprite-Default.shaderです。 同様にSpriteA.prefabが依存しているAssetはTextureA・Sprite-Default Material・Sprite-Default.shaderです。 先程、TextureAのPacking Tagを変更した為、SpriteA.prefabの構成要素の変更があったと判断されalphabetabの再ビルドが実行されましたが、Sprite.prefabの構成要素には変更が無かった為、AssetBundle numbersの再ビルドは実行されなかったということです。

AssetBundle numbersに含まれるSpriteAtlasを更新したい場合、numbersの再ビルドを実行する必要があります。 いちばん手っ取り早い方法は、AssetBundle numbersを削除することでしょう。 若しくはBuildAssetBundleOptions.ForceRebuildAssetBundleを指定して、AssetBundleをビルドするという方法もあります。

SpritePacker

Legacy Sprite Packerと違い、明示的にProject Viewから右クリックでCreate > Sprite AtlasでSpriteAtlasを生成します。 SpriteAtlasへのSpriteの追加は手動で行います。SpriteAlasのInspectorからObjects for Packingの項目にある+を押して、Spriteを追加していきます。 f:id:kimukats:20210714160125p:plain

Spriteを追加した結果はこんな感じです。 f:id:kimukats:20210714160413p:plain

詳しいワークフローはこちらをご確認下さい。

それではAssetBundleをビルドして中身を確認してみます。

f:id:kimukats:20210714162209p:plain 左がnumbersab,右がalphabetabですがぱっと見た感じどちらも同じものです。

では、Numbers.spriteatlasからAを取り除いてAlphabet.spriteatlasを新たに作成しそちらへAを追加します。

f:id:kimukats:20210714162654p:plain f:id:kimukats:20210714162632p:plain

この状態でAssetBundleをビルドしてみます。 ・・・結果は変わりません。SpritePackerの場合でもSpriteAtlasは依存ファイルとはならないようです。 しかたがないので強制的にAssetBundleをビルドしてみます。

f:id:kimukats:20210714165113p:plain 左がnumbersab,右がalphabetabです、期待道理の結果です。

さて、SpriteAtlas自体をAssetBundleにするとどうなるのでしょうか?

f:id:kimukats:20210714172510p:plain

いい感じです。SpriteAtlasがAssetBundle化され、SpriteAtlasを参照していたPrefabのAssetBundleからは消滅しています。 また、SpriteAtlasに変更を加えた後、AssetBundleをビルドするとSpriteAtlasのAssetBundleはビルドの対象となります。

また、SpriteAtlasをAssetBundle化する場合、include In Buildのチェックは外しておくべきでしょう。 このチェックが有効な場合、SpriteAtlasはアプリケーションに含まれることになる為、2重にSpriteAtlasを持つ事になります。

考察

なぜ、SpriteAtlasは依存関係の対象とならいか考察してみます。 運用中のタイトルで考えてみると、何となくその理由がわかってくる気がします。

ある運営タイトルでSpriteAtlasを参照しているAssetBundle Aがあるとします。 あるイベントで新規のスプライトをSprteAtlasに追加し、そのスプライトを参照するAssetBundleBを追加したとします。 この時、SpriteAtlasが依存関係をもってたとすると、AssetBundleAもビルドの対象となり、ユーザーは更新されたAssetBundleAと新規のAssetBunldeBの両方をダウンロードする必要がありますが、現象の仕組みではAssetBundleAは更新されないので、ユーザーはAssetBundleBのみを更新すればよいことになります。 既存のSpriteAtlasを分割する時は、自動更新されないという不満は残りますが、大きな変更ですので明示的に更新を実行すれば良いですし、最悪更新されなくても描画されないという事故は防げます。

結論

  • SpriteAtlasはそこに含まれるSpriteを参照(使用)するAssetの依存関係とはなり得ないので、SpriteAtlasが変更されても参照先のAssetBundleは更新されない。
  • Legacy Sprite Packerで生成されたSpriteAtlasはSpriteAtlas単体でのAssetBundle化は出来ないが、(新)SpritePackerで生成されたSpriteAtlasはAssetである為、SpriteAtlas単体でAssetBundle化出来る。

以上のことから、これから始まるプロジェクトではLegacy Sprite Packerを使用するメリットはありません。 AssetBundleを使用する場合、SpriteAtlasの特殊性を頭の片隅に覚えておくことで思わぬ事故が防げると思います。