テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】AssetBundleからMultiple Spriteに含まれるスプライトを取得する方法とその周辺

f:id:tsubaki_t1:20151113192236j:plain

今回はAssetBundleの中に格納したMultiple Spriteから特定のスプライトを取得する方法と、その挙動についてです。

Multiple Spriteの取得方法について

AssetBundleからSpriteを取得したい場合、それが単品なら単純にAssetBundle.LoadAsset<Sprite>で取得する事が出来ますが、対象がMultiple Sprite、つまりスプライトを分割した内容ならばそうはいきません。

f:id:tsubaki_t1:20151113192719j:plain

AssetBundle.LoadAllAssetsでは駄目

ここでResources.LoadAllと似たようにAssetBundle.LoadAllAssetsと行きたい処ですが、この方法ではAssetBundleに格納されている全てのアセットをロードする事になり、余計なメモリと解放分のCPUが必要となります。

下の画像は、Sprite一つを呼ぶはずがAssetBundleに格納されている全てのテクスチャ(RobotBoy関連)が読まれてしまっている状態です。

f:id:tsubaki_t1:20151113194047j:plain

AssetBundle.LoadAssetWithSubAssets

なのでAssetBundle.LoadAssetWithSubAssetsを使用します。
こちらであれば、AssetBundleから指定のテクスチャから生成したスプライトしか呼ぶことは無いので、上のように無駄なテクスチャが読まれる事はありません。

f:id:tsubaki_t1:20151113194054j:plain

docs.unity3d.com

不要なスプライトの破棄

しかし、まだSpriteが無駄にいっぱい生成されています。これは、ロードしたMultipleSpriteが「複数のスプライトを含む1つのアセット」である事に由来してると思います。

これ一つ一つは大した問題では無いのですが、使わないのであれば気持ち悪いので使うものだけ残します。

Resource.Unloadで使っていなければ一つ一つ潰しても良いのですが、Resources.UnloadUnusedAssetsで一気に潰してしまいます。

f:id:tsubaki_t1:20151113194809j:plain

これで、ひとまずはSpriteを単体ロードする事が出来ます。
下のコードは、StreamingAssetsに格納したplayerアセットバンドルの、RobotBoyJumpSpriteテクスチャが持っているRobotBoyJump00スプライトを取得する例です。

gist.github.com

特定のスプライト参照からスプライトを取得する

これとは別に、Spriteを単体で取得する方法もあるにはあります。要するに特定のSpriteへの参照を元にスプライトを取得するアプローチです。

これ以外でも、とにかく「他のComponentやScriptableObject、Animation等から参照されている」場合、該当のスプライトのみを抽出することが出来ます。

f:id:tsubaki_t1:20151113200939j:plain

この方法で取得した場合、AssetBundle.LoadAssetWithSubAssetsと比較してスプライトが生成しませんしロード後の検索も不要です。

ただし実装にひと手間付きます。

gist.github.com

必要な手間は二つ、

  • Multiple Spriteの持つスプライトをSpriteScriptableObject に格納する
  • SpriteではなくSpriteScriptableObject をAssetBundleに格納する
  • SpriteScriptableObjectをロードしスプライトを抽出する(LoadAB)

f:id:tsubaki_t1:20151113202024j:plain

f:id:tsubaki_t1:20151113202151j:plain

これでスプライトをロードした場合、スプライトちゃんと単品で呼ばれます。Resources.Unloadで消してやれば一緒に呼ばれるScriptableObjectもちゃんと破棄されます。

f:id:tsubaki_t1:20151113202423j:plain

ちなみに、こうして指定スプライトを抜きだした場合でもテクスチャは1枚分まるっと読まれます。そうしないと、「テクスチャをロード後、スプライトが表示する範囲のテクスチャを生成し、それを元にSpriteを生成し…」みたいな事になります。

DynamicFontが似たような事やってますが、Spriteではやってないのでやはり負荷が…圧縮したTexture使えなくなりますし。

SpritePackerでAtlas化したMultiple Spriteと挙動

Resourcesと異なり、AssetBundleはSpritePackerでパックしたスプライトもロードすることが可能です。

その際、AssetBundle.LoadAssetWithSubAssetsから呼び出した時のスプライトおよびテクスチャは、「テクスチャはパック済みの内容」「スプライトはテクスチャ単位」で取得されます。

f:id:tsubaki_t1:20151113203817j:plain

どういった状況かと言えば、こんな感じです。

まずはSpriteをSpritePackerでパックします。元々は2048x2048のスプライト1枚なのですが、パックする事で無駄な領域が消え1024x512となります。名前はSpriteAtlasTexture-Packer云々。

f:id:tsubaki_t1:20151113204038j:plain

f:id:tsubaki_t1:20151113204045j:plain

で、これをロードすると、スプライトの元となるテクスチャはAssetBundle1枚ですが、呼び出し生成するSpriteはMultipleSpriteに依存するので、スプライトが一杯作られた…そんな感じだと思います。

このため、AssetBundleからスプライトを取得する場合、Multiple Spriteからスプライトを生成するよりScriptableObject等から特定のスプライトを参照するなり、単体のスプライトをSpritePackerでまとめた方が良いかもしれません…が、PrefabやAnimationからSpriteを参照する場合は普通に読めるので、これは「Spriteをランタイムに取得して差し込む」ようなケース以外ではそれほど重要ではないかもしれません。

関連

tamilabo.blog.fc2.com

tsubakit1.hateblo.jp