テラシュールブログ

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

【Unity】iOS/Androidでアプリを休止してもダウンロードを続ける BackgroundDownload

今回はタイトルの通り「アプリを終了(もしくはサスペンド)してもダウンロードを継続する」機能を提供するアセットの機能と使い方についてです。

このアプローチを導入すると、アプリ開始時の「追加コンテンツダウンロード待ち」でアプリを起動しっぱなしで待たずとも良くなります

アプリのバックグラウンドでダウンロード

通常のWebRequest系は非同期で動作しますが、基本的にダウンロード処理はアプリが動作していないと動きません。このため、多くの場合ダウンロード中に色々と工夫が加えられています(漫画を表示とか、ミニゲームとか)

今回のアプローチでは、アプリを休止もしくは停止した状態でもダウンロードを継続してくれます。つまり、このダウンロード時間を他の操作(例えばTwitterを見るとか!)に使うことが出来ます。

単純に消費電力の低下にも良いかもしれません。

電池消費のそれなりの割合はGPUと通信(4G)です。Unityは毎フレームGPUを動かすゲームエンジンなので、ゲーム画面でダウンロード待ち処理を行うと、GPUと通信でバッテリー消費量が増大しますし、端末も熱くなります。逆にGPUを止める(フレームレートを超落とす、CameraとCanvasを全て止める)と、Energy ImpactLowに下がります。*1

さらにアプリをバックグラウンドに持っていけば、メインスレッド/ワーカースレッドも、GPUも動いていない状態になるので、もっと消費電力を下げる事が期待出来ます。

f:id:tsubaki_t1:20181216131027p:plain
レンダリングを止めてGPUを抑えた時の消費電力

BackgroundDownloadの概要

github.com

BackgroundDownloadiOS/Android/Windows Platform向けプラグインです。このプラグインはアプリのバックグラウンド*2でファイルのダウンロードを可能にします。

ダウンロードしたファイルはApplication.persistentDataPath以下に保存されます。BackgroundDownloadにはダウンロードしたbytesにアクセスするインターフェースが無いので、System.IO系のAPIダウンロードしたファイルへアクセスする流れになります。

f:id:tsubaki_t1:20181217002109j:plain
ダウンロードと利用は完全に分かれている

BackgroundDownloadWindowsMac、というよりUnity Editorで動作しません。
エディター上で動作をテストする場合は、BackgroundDownloadがダウンロードしたファイルを保存するフォルダに、ダウンロードすべきファイルを置いて*3動作確認をする流れになるんじゃないかなと思います。

導入

  1. GitHub - Unity-Technologies/BackgroundDownload: Plugins for mobile platforms to enable file downloads in backgroundBackgroundDownloadフォルダとPluginsフォルダをUnityプロジェクトに入れます。

iOSなら、コレで解決します。

Androidの場合、Manifestを少し弄る必要があります。

  1. Manifestを用意
  2. <receiver android:name="com.unity3d.backgrounddownload.CompletionReceiver"></receiver><application>の中に追記
  3. <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />を追加

要するに、下の項目を自身のManifestへ移植します。特にjava.lang.SecurityExceptionが出た場合、マニフェストが正常に設定されていません。

f:id:tsubaki_t1:20181217002559j:plain
自身のManifestファイルに移植すべき項目

使い方

後は使うだけです。

APIは非常に簡単で、BackgroundDownloadConfigにダウンロードしたいファイルや設定を詰めてBackgroundDownload.Startを呼ぶだけです。
コルーチンで簡単に観測出来ますが、BackgroundDownload.backgroundDownloads[0]で他の機構から観測するのも良さそうな感じがします。

BackgroundDownloadConfig config = new BackgroundDownloadConfig
{
    filePath = saveFilePath,
    url = new Uri(downloadFileURL),
    policy = BackgroundDownloadPolicy.UnrestrictedOnly
};

using (var download = BackgroundDownload.Start(config))
{
    yield return download;

    Debug.Log(download.status);
}

BackgroundDownloadConfigでは、一応、AddRequestHeaderでヘッダー情報を追加したり、ダウンロードが使用できるネットワークの指定*4が可能です。

gist.github.com

ダウンロード完了時や、ダウンロード進行度を通知で出したい

現状、プラグイン自体を書き換える必要があります。

f:id:tsubaki_t1:20181217003856j:plain
Androidの通知にダウンロード進捗を表示

補足

  • Q : iOS でバックグランドのダウンロードは5分かそこらでOSから強制的に落とされる?
    A:backgroundTaskではなくNSURLSessionDownloadTaskを使用しているので、多分そういうことは起こらない
  • Q:沢山のファイルをダウンロードしようとすると、セッションの限界に到達しない?
    A:ダウンロード処理は最大4つ並列で処理され、他はキューィングされる。

感想

毎回長時間のダウンロードを待たされる身としては非常に良い機能に見えますが、コレ自体はかなり昔から実現可能なアプローチであり、その上でコレを利用していないゲームが過半数を占めるということは、何か問題があるのかもしれません。(例えばバックグラウンドにした後にプレイヤーが帰ってこない等のサービス的な問題とか、技術的な要因とか)

何にせよ簡単に出来るようになったのは有り難い。

*1:通常はHih ~ Very Highの間ぐらい

*2:アプリの裏側という意味、別スレッドという意味ではない

*3:ダウンロードに成功したという扱い

*4:iOS以外

【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

【Unity】Timelineからメソッドを呼ぶ新機能 「Marker」と「Signal、Signal Receiver」

今回はTimelineから特定のメソッドを呼ぶ方法について紹介します。

この機能はUnity 2019.1 a11から安定して使用出来ます。
これはベータですらないアルファの機能なので、今後変化する可能性があります。

Timelineからメソッドを呼ぶ仕組み

Timelineではアニメーションイベントと区別するためか、「Signal」という新しい概念を追加しました。

Signalはアセットです。これはScene上のSignal ReceiverとTimelineに登録します。TimelineからSignalを呼び出すと、シーン上に存在するSignal Receiverシグナルを受信し、Signal ReceiverがUnity Eventを呼び出す流れです。

f:id:tsubaki_t1:20181210222329j:plain

名称 機能
Signal アセット、マーカーに登録する
Marker Timelineに登録するイベント
Signal Receiver マーカーからシグナルを識別して、UnityEventを呼び出す

SignalやSignal Receiver、マーカーを使う流れ

使い方を見てみます。今回は下のような、特定のタイミングでパーティクルを出力してみます。*1

f:id:tsubaki_t1:20181210224251g:plain

まず最初に、Signalアセットを作ります。
名前は何でも良いのですが、全体を通してユニークな名称である必要があるので、Signalフォルダとか作って、全てソコに突っ込むのが良さそうです。

今回はEmitというSignalを作りました。

f:id:tsubaki_t1:20181210223748j:plain

次にシグナルで反応して欲しいGameObjectにSignal Receiverを登録します。

今回は、移動する玉(Sphere)オブジェクトのParticle System.Emit(100)を呼び出してみます。 Timelineのシグナルと関連付ける為、Signalには先程作成したシグナル、Emitを登録します

f:id:tsubaki_t1:20181210224057j:plain

あとはTimelineのアニメーションクリップ上でAdd Signal Emitterを選択し、マーカーを追加します。
マーカーのシグナルはEmitを選択すると、そのタイミングに呼ばれるイベントが何かを確認出来ます。

f:id:tsubaki_t1:20181210224710j:plain

Tips

呼び出されるマーカーは、Trackが指定しているオブジェクトのSignal Receiverです。同じSignalを使用している場合でも、異なるSignal Receiverの場合、各々のイベントが呼ばれます。

呼び出すSignal ReceiverはTimelineのTrack指定ですが、その他にもSignal Receiver自体をTrackに登録するSignal Receiver Trackや、Playabel Directorが登録されているオブジェクトに設定されているSignal Receiverを呼び出す、Marker Trackなどがあります。

f:id:tsubaki_t1:20181210225200j:plain

Signal ReceiverのUnityEventをEditor And Runtimeにすると、動かせるAPIならゲームを再生しなくてもイベントを呼び出す事が出来ます。その場合、当然巻き戻し処理を作っておかないとひどい目になります。

f:id:tsubaki_t1:20181210230157j:plain

マーカーの挙動を自作する

マーカーを自作することも出来ます。
こちらの方は、Signal等を用意せずともよく、コードベースで色々な処理を実装可能です。 特にSignalベースだと、マーカーに複数の情報を登録する場合*2Signalが増えすぎて不便なので、場合によってはコチラの方が便利かもしれません。

今回はTimelineから文字を切り替える処理を作成してみます。
マーカーに到着したタイミングで文字を切り替えています。

f:id:tsubaki_t1:20181210230613g:plain

自作するコードには、送信するマーカーと、マーカーを受信するコンポーネントが必要です。

gist.github.com

Text Marker ReceiverSignal Receiverの代わりに登録します。Timelineからこのクラスが呼ばれる感じです。

f:id:tsubaki_t1:20181210232702j:plain

次にマーカーを受信するGameObjectを登録するTrackを追加します。Animation等で動かしているなら該当するTrackに足せば良いですが、特に無いのならばMarker Trackが良いです。

あとはMarker Trackにイベントを登録します。上のコードを登録しておくと、登録するマーカー一覧にテキストのマーカーが追加されているので、登録します。あとはマーカーの中身をInspectorで設定していきます。

f:id:tsubaki_t1:20181210231136j:plain

f:id:tsubaki_t1:20181210231312j:plain

これでゲームを再生するとイベントが呼ばれます。今回の場合は、テキストの中身が差し替わります。

感想

Signalが直感的ではない感じがしましたが、複数のTimelineを複数のSceneで使いまわしたり、イベントを沢山配置する用途だと確かに理にかなっている気がします。

関連

Signalについて

New in 2019.1 - Timeline Signals - Unity Forum

マーカーのカスタマイズ

New in 2019.1 - Marker Customization - Unity Forum

AnimationEventのように、特定のメソッドを呼び出す、カスタムマーカーのサンプル

github.com

Unity 2019.1未満ならコチラ

tsubakit1.hateblo.jp

わかりやすいスライドが公開されました

speakerdeck.com

*1:本当はCinemachineの画面揺らしをしたかったんですが、TimelineがPackageManagerに移ったせいで動作しません。

*2:例えば字幕のような、二度と使わないイベントを大量の登録する場合

【Unity】SceneViewのカメラが回せなくなった時の対処法

f:id:tsubaki_t1:20181209190233g:plain

SceneViewが回せないという動作になりました。
コレの解決法についてです。

SceneViewのロックを外す

結論から言えば、SceneViewのロックが入っていた為でした。
ロックされていると、右上のGizmoの色が薄くなります。

f:id:tsubaki_t1:20181209190720j:plain

なので、このロックを外してやれば、期待する動作に戻ります。

f:id:tsubaki_t1:20181209190633g:plain

タイミング的にProject Tinyを使ったらこうなったのかな