テラシュールブログ

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

【Unity】SpriteAtlasでパッキングしたテクスチャを遅延ロードする

今回は、キャラクターやUIといったシーンに配置したSpriteのテクスチャを遅延ロードする事で、ユーザーが操作出来るまでの時間を短縮するアプローチついてです。

SceneやPrefabから参照するSpriteは即時ロードされる

Unityを使っていて頭を悩ませる項目の一つが「SceneやPrefabが参照しているアセットは、オブジェクトがロードされた瞬間に即時ロードされる」という点です。これはロードが完了すればレスポンスが高く、色々と処理を考えなくても良いのですが、同時にロードが完了するまで何も出来ないという欠点もあります。

これを回避するため、Scene/Prefab全体でUIを構築するのではなく、Prefabを個別にロードする、もしくはPrefabからSprite等の情報を剥がしておき追加で注入するアプローチをよく採用されています。ただし、実装の難易度が上がったり、作成ルールが厳格かつ複雑になります。

さて、問題はやはりTextureでしょう。他のアセット群のように即時必要になる訳でもなく、かつデータサイズも非常に大きい。なので、今回は「Textureは後で読む」アプローチを使ってみます。

テクスチャの遅延ロード

よく勘違いされている事ですが、Unityの使用するSpriteはTextureを指すのではなく、MeshとTextureを含むデータを指します。メッシュのUV情報を使用してテクスチャの一部を切り出す感じです。
この2つはある意味分割されているデータなので、後で更新することが可能です。

なので"Spriteが持つUV情報"と"空のTexture"だけを事前にロードしておき、その空のTextureに後でAtlasを注入するといった事を行います。

Textureをロードされないようにする

まず最初に、Spriteを使用してもTextureも一緒にロードされないようにします。

  1. 遅延でロードしたいSpriteをSpriteAtlasに登録
  2. SpriteAtlasのビルドに入れるのチェックを外す

f:id:tsubaki_t1:20181214211010j:plain

これでビルドした時、Spriteを使用していてもSpriteが使用するTextureはロードされなくなります。
ロード時にはSpriteAtlasManager.atlasRequested wasn't listened to while SpriteAtlas名 requested.と表示されます

f:id:tsubaki_t1:20181214212333g:plain

動的にSpriteAtlasをバインドする

次にTextureをバインドします。

今回はAssetBundleでSpriteAtlasを配信しようと思うので、AtlasはatlasAssetBundleに格納しています。
暗黙的参照で含まれているように見えますが、ポリゴン情報だけです。

f:id:tsubaki_t1:20181214213047j:plain

後はSpriteAtlasManager.atlasRequestedでSpriteAtlasのリクエストを検出、そのタイミングでAtlasをロード・差し込みを行えばOKです。
この機能はゲームで一つだけあれば良いので、今回はこの動作をするマネージャーをScriptableObjectにして、プリロードアセットに登録しました。他にもシングルトン的なアプローチでも良いです。

gist.github.com

f:id:tsubaki_t1:20181214214224j:plain

f:id:tsubaki_t1:20181214214235j:plain

これを実行すると、シーン上に用意したオブジェクトをロード後、SpriteAtlasで使用しているSpriteが追加ロードされ反映されます。

f:id:tsubaki_t1:20181214215008g:plain

なお、このアプローチはビルドしたゲーム及びAssetBundleの双方にポリゴン情報が乗ります。テクスチャの本体と比べれば僅かな情報ですが。

f:id:tsubaki_t1:20181214220912j:plain

解像度を下げたテクスチャをロード

設計上、ロード対象は簡単に差し替えられます。 今回は最大解像度ではなく、解像度を下げて省電力モードします。端末によっては圧縮フォーマットの切り替え等もありかもしれません。

  1. SpriteAtlasを作成し、バリアントを指定。 先程作成したSpriteAtlasをマスターに登録
  2. 両方共AssetBundleに格納
  3. SpriteAtlasManager.atlasRequestedのコールバック時に、どちらかのSpriteAtlasを指定
    (追加文字列で読み込むSpriteAtlasを切り替える。上のコードではtagがSpriteAtlas名と一致)

f:id:tsubaki_t1:20181214222656j:plain

f:id:tsubaki_t1:20181214223303j:plain

f:id:tsubaki_t1:20181214222959j:plain

unity 2018.3でUIにも対応

この機能、以前はSpriteRenderer専用だったのですが、Unity 2018.3でUI(Image)にも対応しました。
これで最初のUIを表示しつつ裏で次のUIのTextureをロード、画面の切り替えはCanvasとGraphicsRaycastのOn/OFFを切り替えるだけ…というのが実現出来ます。

UI: Added Sprite Atlas Late-Binding support for UI.

UIの描画順とかをSceneで設定しつつも、遅延ロードが可能という感じです。

f:id:tsubaki_t1:20181214220209j:plain

SpriteAtlasの個別アンロード

このアプローチ、一旦SpriteAtlasをアンロードしてしまうと「現在存在するSpriteRendererやImageに入れ直す」事が出来ません。Atlasを開放すると次に使えるのは、Spriteを参照してるPrefabを生成したタイミング(反映されるのは開放以降に生成したSpriteのみ)となります。

テクスチャは誰も使用していなければ開放→使用のタイミングで再ロードとなります。ただ「使ってない」判定にはSpriteへの参照を全て外さなくてはいけないので、細々とメモリを足したり消したりする場合には少し面倒かもしれません。

(個人的には、トップメニューのUIとかは全部テクスチャ読みっぱなしでも良いんじゃないかって気がしますが)

感想

「ユーザーが操作出来るまでの時間を短縮」しつつ「複雑なスクリプト制御を削る」って用途がマッチしていそうです。

関連

「SpriteAtlasとAssetBundleの組み合わせで問題が…」という場合、使い方が間違ってます

tsubakit1.hateblo.jp