FC2ブログ
ヽ|∵|ゝ(Fantom) の 開発blog? ホーム »Unityライブラリ
このページの記事一覧

【Unity】【C#】アセットバンドルのキャッシュを調べて不要なものを消す  


 もう随分 AssetBundle Manager を触ってなかったのだが、ちょっと利用する機会があったのでメモ。

 どうやら Unity2017 以降にはキャッシュの操作もできるようになったみたいなので、キャッシュの存在確認や不要なキャッシュを簡単に削除できると良いと思った。

 この AssetBundle Manager はUnity5 時代のもので、もうアセットストアからは落とせないみたいだが、まだお世話になってる人のためにも機能追加案を書いておこう。他のツール使ってるなら必要ないかもだが、同じような考え方なら流用もできるだろう。


(※) Unity2018.3.9f1 / Windows10(x64) で確認



●マニフェストからハッシュを取得
//マニフェストからハッシュを取得
static public Hash128 GetAssetBundleHash(string assetBundleName)
{
return m_AssetBundleManifest.GetAssetBundleHash(assetBundleName); //※先にマニフェストをロードしておく必要あり
}

 この関数はレガシーな AssetBundle Manager を基にしてるので注意(スコープや書き方も合わせている)。

 これは元からある AssetBundleManifest.GetAssetBundleHash を少し簡単にしただけのものなので、あまり説明はいらないだろう。このハッシュは実際にマニフェストに書かれているもので、テキストエディタなどで「~.manifest」を開けば、それぞれの「AssetFileHash」に対応していることがわかる。もう結構古い記事になるがテラシュールさんの記事に詳しく書いてあるので、参考資料として載せておこう。

AssetFileHashとTypeTreeHashは、AssetBundleの更新判定
AssetBundleManifest.GetAssetBundleHash



●アセットバンドルがキャッシュに存在しているか?(url と hash を用いる)
//アセットバンドルがキャッシュに存在しているか?
static public bool IsExistCaching(string assetBundleName)
{
var url = BaseDownloadingURL + assetBundleName;
var hash = GetAssetBundleHash(assetBundleName);
return Caching.IsVersionCached(url, hash);
}

 この関数もレガシーな AssetBundle Manager を基にしてるので注意(スコープや書き方も合わせている)。GetAssetBundleHash は前述したものだ。

 なぜだかわからないが、この関数はわりと昔からあって(Unity5 の時代からあったらしい?)、マニュアルにも載ってるバージョンとそうでないバージョンがある。

 IsVersionCached(string url, int version) の方は Obsolute(廃止予定)だし、version の指定はどちらかというと、アセットバンドル全体のバージョンという感じで、個々のアセットバンドルの差分としては使いづらいんだよね(この version は WWW.LoadFromCacheOrDownload [obsolute] や UnityWebRequestAssetBundle.GetAssetBundle などの引数に当たる)。なので、ハッシュの方を使いたいことが多い。まぁ、url 指定なので少し使いづらいが…。

 また、ハッシュと任意の名前を用いることもできる。ここでは名前をアセットバンドル名としておくと、以下のようなものも作れる。

●アセットバンドルがキャッシュに存在しているか?(url と name を用いる)
//アセットバンドルがキャッシュに存在しているか?
static public bool IsExistCaching(string assetBundleName)
{
var hash = GetAssetBundleHash(assetBundleName);
var cab = new CachedAssetBundle()
{
hash = hash,
name = assetBundleName,
};
return Caching.IsVersionCached(url, hash);
}

 こちらの方が使いやすいかも知れない。url をキーとしてないので、移動しても使える。

 ついでに色々資料も載せておこう。

キャッシュの存在確認
Caching.IsVersionCached
WWW.LoadFromCacheOrDownload [obsolute]
UnityWebRequestAssetBundle.GetAssetBundle



●現在のハッシュ以外のキャッシュを消す
//現在のハッシュ以外のキャッシュを消す(※使うタイミングに注意)
static public bool ClearOtherCaching(string assetBundleName)
{
var hash = GetAssetBundleHash(assetBundleName);
return Caching.ClearOtherCachedVersions(assetBundleName, hash);
}

 この関数もレガシーな AssetBundle Manager を基にしてるので注意(スコープや書き方も合わせている)。GetAssetBundleHash は前述したものだ。

 これは割と最近(Unity2017.1 以降?)にできてたらしい。調べていたら出てきたので、試してみたら上手く行った。実行は同期的なのでタイミングには気をつける必要があるが(まだ古い使用中アセットがあった場合、途中破棄されるわけで)、他にも削除する方法はあるみたいなので、色々やってみるのも良いかも知れない。また、資料には「デフォルトで150日間で期限切れ」ともあるね。以下に記事も載せておこう。

[Unity 2018.2] AssetBundleのキャッシュを完全に理解する
期限切れによる削除
Caching.GetCachedVersions



 まぁ、この AssetBundle Manager 自体は旧時代のものなので、これからも使うなら少し書き直した方が良いかも知れない。WWW よりも UnityWebRequest の方がパフォーマンス良いと聞いたこともあるし、実際に書き換え例なんかもあるので、自分でやってみると良いかも(Unity2019 には Addressable Assets というものも出てくるらしいしので今更だが(笑))。

AssetBundleManagerのwwwをUnityWebRequestに変えて、さらにキャッシュの古いバージョンを削除してみる






(関連記事)
【Unity】【C#】AssetBundleManager を WebGL で使う・エラー対処法
【Unity】【C#】Addressable Assets でキー(アドレス)の存在(登録)を調べる


スポンサーサイト



category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  Unityオープンソースライブラリ 
tb: 0   cm: --

【Unity】Firebase のプッシュ通知と FantomPlugin の共存(Firebase と他のプラグインの共存方法)  


 ちょっと最近 VRM Live Viewer に構いっきりで(笑)、FantomPlugin の更新の方が滞っているのだが、Android 8.0 以降では通知システムも変わってしまったので、試しに Firebase のプッシュ通知をオーバーライドできるかやってみた。結果はそれほど難しく無く可能だったので、細かい部分はカスタマイズした方が良いと思うが、導入もできると思う。あくまで簡単な例となるが、FantomPlugin と Firebase の共存の方法を提供しておこう。

 ただ、この機能は Unity2018.2.x 以降のプロジェクト内で Java をプラグインとして扱えるようになったおかげで使えるようになったものなので、必要であれば、以下の記事も参照して欲しい。

【Unity】【C#】プロジェクト内で Android(Java)プラグインをビルドする


(※) Unity2018.3.5f1 (Unity 2018.2 以降) / Firebase SDK 5.5.0 / Windows10(x64) で確認



■プラグインの導入

 大まかには Firebase SDKFantomPlugin の2つを用意することになる。といっても、ここではオーバーライドするプラグインの例に FantomPlugin を使っているというだけで、考え方さえわかれば、他のプラグインに Firebase を利用することも可能だろう。



●Firebase SDK のインポート(掲載時点 SDK ver.5.5.0)

 Firebase のプッシュ通知を利用するには、公式から配布されている Unity 用のパッケージを導入する必要がある。以下のページに「SDK をダウンロード」というボタンがあるので、クリックして zip をダウンロード→解凍して欲しい。

Unity プロジェクトに Firebase を追加する


 zip を解凍すると、「dotnet4」というフォルダがあるので、その中から「FirebaseMessaging.unitypackage」をインポートしよう。これには Firebase からプッシュ通知を受け取るアクティビティやマニフェストファイル、API などが含まれている。初回のインポートでは依存関係のライブラリを自動導入するので、結構時間かかるかも知れない(※Build Target が Android になってない場合はここでやっておくと良い)。また、「dotnet3」にも同じものが入っているが、これは .NET 3.x 時代のもので、Unity2018 ではデフォルトで .NET 4.x となっているので、ここでは不要だ。

 Resolver(依存関係の解決)が終わったら、大量のファイルが「Assets/Plugins/Android」に配置されていると思う。Firebase を既存のプロジェクトに使うだけなら、ここにある「AndroidManifest.xml」はあまりいじる必要はないが(だいたい自動でやってくれる)。他のプラグインと共存するには少し手を加える必要がある。その辺りは後で説明しよう。



●FantomPlugin のインポート

 とりあえず今回はアセットストア版の「FantomPlugin (Android Native Dialogs and Functions Plugin)」(※長いので略してるだけ(笑))を使って説明しよう。Google Drive 版を使っている人でも基本的には同じなので、パス名などを置き換えてもらえばできると思う。

 より詳しいセットアップは「AssetStore版 FantomPlugin のセットアップ」の記事にも書いてあるが、簡単な方法としてはエディタ上で [Ctrl+9] でアセットストアを開き「Android Native Dialogs and Functions Plugin」を検索すれば、すぐに出てくると思うので、とりあえずダウンロード→インポートして欲しい。





■FantomPlugin オーバーライド用 Acrivity の導入

 インポートが終わったら、もう1つプロジェクトに追加して欲しいものがある。それは先ほど保留したAndroidManifest.xml」 に関わる問題でもある。というのは Firebase SDK のインポート で作られたマニフェストファイル(AndroidManifest.xml)は Firebase 専用となっている。この辺りの説明は少し難しくなってしまうのだが、なるべく簡単に説明すると、この「Firebase 専用のものをオーバーライドすることにより、他のプラグインの機能を共存してしまおう」ということだ。そのオーバーライドするのに必要な Activity を用意した。そのうち、アセットストア版にも出すつもりだが、とりあえずは先行配布としてこれを使って欲しい。


 ダウンロードして zip を解凍すると「Firebase」フォルダに「OverrideFantomPluginOnFirebaseMessagingActivity.java」というのが入っていると思う。これは現在使われている「FantomPlugin」と同じもので、Unity2018.2 のプラグイン用の Java コードをプロジェクト内でビルドするために作ってみたものだ(内容的にはプラグイン「fantomPlugin.aar」の内部に入ってる「FullPluginOnUnityPlayerActivity」と全く同じ)。


 また、「AndroidManifest_FirebaseMessaging.xml」というのも入っているが、これは Firebase の「AdnroidManifest.xml」に「OverrideFantomPluginOnFirebaseMessagingActivity」をオーバーライドするように修正したものだ。Firebase のデフォルト設定に追加しただけのものなので、必要あれば編集した方が良いかも知れない。

 これらをプロジェクトのプラグインルート「Assets/Plugins/Android/」以下に置いて欲しい。ここに Java ファイルを置いておけば Unity が自動でビルドしてくれる(Build System が「Gradle」のときのみ[※Unity2018 はデフォルト])。

 また、元の「AndroidManifest.xml」はリネームして「AndroidManifest_old.xml」とでもしておき、「AndroidManifest_FirebaseMessaging.xml」を「AndroidManifest.xml」にリネームすると、オーバーライドが有効になる


 この辺りの内容は少し難しくなるので、簡単に説明にしておくと、「UnityPlayerActivity(Unity)継承→ MessagingUnityPlayerActivity(Firebase)継承→ OverrideFantomPluginOnFirebaseMessagingActivity(FantomPlugin)」のようにオーバーライドされていることになる(※つまり自作するときは同じように、競合しないようなオーバーライドする Activity を作れば良い)。

 必要なら参考資料を載せておこう。ダウンロードした zip の中の「Default」フォルダがあると思うが、その中にある「OverrideFantomPluginOnUnityPlayerActivity.java」はデフォルトの「UnityPlayerActivity」を継承している。Firebase のオーバーライドと同じように継承するものを変更すれば、他のプラグインもオーバーライドできるかも知れない。

UnityPlayerActivity Java コードの拡張
Activity
アプリ マニフェスト



■Firebase の準備

 アプリ方の準備はだいたい終わったので、次のプッシュ通知を送信する Firebase自体をセットアップしよう。

1.Firebase をまだ利用してない人は、まずは登録しよう。といっても Google アカウントを持っていれば、サイトに行けばすぐに新規登録できると思う。登録完了したら、いつでも画面の右上あたりにある「コンソールへ移動」があるので、プロジェクト管理へ移動できる。


2.コンソール(「Firebaseへようこそ」)へ来たら、プッシュ通知を利用するプロジェクトを作成しよう。「+プロジェクトを追加」を押して、プロジェクト名などを任意に入力し、「プロジェクトを作成」ボタンを押せば良いだけだ。完了したら、プロジェクトへ移動しよう。



3.初回の場合、プロジェクトにはアプリが追加されてないので以下の画面が出ると思う。今回は Android のプラグインをオーバーライドする実験なので、Android のアイコンを押そう。



4.「アプリの登録」の「Android パッケージ名」は Unity の Identification と合わせておく必要がある(※以下のアプリIDは例なので、自分のアプリのIDに置きかえる)。Unity の PlayerSettings(File>Build Settings...>Player Settings...>Other Settings>Identification)を開いてコピペすると良い。ここが1文字でも間違ってたら、通知は届かないので注意しよう。



●Unity 側

※FantomPlugin を使う場合、Android 4.2 以上にする必要がある。


5.これら Firebase の設定を Unity に認識させるために、設定ファイル「google-services.json」をダウンロードしておく必要がある。このファイルは Unity の Assets 直下に置くことで反映される(ID の紐づけ)



●Unity 側


6.次の「Firebase SDK の追加」は Unity の場合、自動でやってくれるので必要ない。



7.最後の「アプリを実行してインストールを確認」はスキップしてしまって良いだろう。





■アプリをビルドする

 これまでの準備が整ったらアプリをビルドしてみよう。注意すべき点は、「OverrideFantomPluginOnFirebaseMessagingActivity.java」「AndroidManifest_FirebaseMessaging.xml(AndroidManifest.xml)」「アプリのID(Android パッケージ名/Identification)」「google-services.json」だ。結構大変だが、置く場所なども含めて確認して欲しい。

 確認が取れたら、ビルドするシーンは何でも良いが、FantomPlugin のデモなら、「FantomPlugin/Demo/Scenes/」以下に置いてある。それらを「Build Setting>Scenes In Buildに追加し、「Player Settings...」で「Minimum API Level」が 4.2 以上(※これは FantomPlugin の仕様。できれば 5.0 以上の方が良いかも知れない)になってればOKだ。ビルドして一度起動してみよう。また、今回は通知を受信する実験をするだけなので、アプリはすぐに終了して構わない。







■Firebase からプッシュ通知を送る

 ここまででの手順が間違ってなければ、既に Firebase からプッシュ通知の送信ができるようになっている。Firebase のコンソール へ行き、左のメニューから「Cloud Messsaging」へ移動しよう。初回なら「Send your first message」のボタンが出るので、それを押すとメッセージを作成することができる。



1.通知の作成画面では任意にテキストを入力して行こう。気をつけて欲しいのは既にリリースされているアプリの場合、適当に入力したものがユーザーに届いてしまうことである。その辺は自己責任で(笑)。



2.一番重要なのは、このターゲットのアプリ(ID)だ。ここを Unity のアプリの Identification と同じにすることによって、そのアプリ用の通知として送られる。間違えないようにしよう(Android / iOS などでも分別できる)。



3.以降の「スケジュール設定」は「Now」(今すぐ)で、「コンバージョンイベント」はデフォルトで良いだろう。「その他のオプション」では「Android 通知チャンネル」という項目があるが、これは Android 8.0 以降の「通知チャンネル」の機能で、通知ごとに種類を分別できるようにするものだ(例えば「お知らせ」とか「更新情報」とか、別々に通知のオン・オフできるようにするため)。Android 8.0 より前の機種では分別はされない。

Android O(APIバージョン26)のPush通知



4.最後の「メッセージの再確認」では「公開」ボタンを押すと即送信されるので(スケジュールで「今すぐ送信」(Now)の場合)、間違いがないか確認してから押そう。ユーザーに飛んでいってしまうので、もう取り消せない(笑)。



5.履歴が表示されていれば、送信完了だ。通信状態・サーバー状況などによって、タイムラグが出ることもあるが、成功していればおおよそすぐに通知が来ると思う。実機で確認してみよう。





※アイコンは Unity で設定したものが表示される(空だと Android のデフォルトアイコンになる)






(関連記事)
【Unity】【C#】プロジェクト内で Android(Java)プラグインをビルドする
【Android】【Java】パーミッションの付与(許可)のチェックと要求をする
【Unity】Androidのトーストやダイアログ、通知、音声認識、ハード音量操作など基本的な機能を使えるプラグインを作ってみた
【Unity】AssetStore版 FantomPlugin のセットアップ


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  Unityプラグイン  Unityオープンソースライブラリ  Firebase 
tb: 0   cm: --

【Unity】【C#】Addressable Assets でキー(アドレス)の存在(登録)を調べる  


 新しいリソースの管理のシステムである Addressable Assets をちょっといじってみた。しかしなぜか、素材のアドレス(キー)が存在しないときに出る InvalidKeyException が try~catch で捕捉できないので、登録の有無の検出ができなかった…(掲載時点:Addressables System 0.5.3-preview)。


InvalidKeyException encounterd in operation UnityEngine.ResourceManagement.CompletedOperation`1[UnityEngine.Texture], result='', status='None', valid=True, location=..
UnityEngine.AsyncOperation:InvokeCompletionEvent()

InvalidKeyException: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown., Key=hogehoge
UnityEngine.AsyncOperation:InvokeCompletionEvent()

 まだ preview 版なので、単なるバグなのかも知れないが、少し不便と感じたのでググってみたら、フォーラムにも同じ内容があった。

Can I ask Addressables if a specified key (addressable name) is valid?

 まぁそのうち、ちゃんとエラー捕捉できるようになったり、「Addressables.HasKey()」みたいなのができそうな気がするが、今使うには簡単なものを用意しておくのも良いだろう。



(※) Unity 2018.3.2f1 / Addressables System 0.5.3-preview / Windows10(x64) で確認



■Addressable Assets にアドレス(キー)が存在するか否か?を返すメソッドを定義

 フォーラムにある「AddressableResourceExists」でも判別可能ではあったが、そのすぐ下に Unity の中の人からのアドバイスが書かれていたので、試しにやってみた。

What you have above probably could work. But the thing I would actually recommend is to just do Addressables.LoadAsset<IResourceLocation>("key");

This will not load your asset, but will load the location that points to your asset. If your key is valid, then you'll get a valid location back. Otherwise you will not. You can keep and use that location (doing LoadAsset<GameObject>(myNewLocation)) or you can just discard it and continue to load with key once needed.

 Google翻訳すれば『これはアセットをロードするのではなく、アセットを指す場所をロードします。キーが有効であれば、有効な場所に戻ります』となるが、要するに「存在位置が示されればアセットは存在する」ということだろう。

●Addressable Assets にアドレス(キー)が存在するか否か?
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement;

public static class AddressablesUtil { //※クラス名は任意

// (参考) https://forum.unity.com/threads/can-i-ask-addressables-if-a-specified-key-addressable-name-is-valid.574033/
// http://fantom1x.blog130.fc2.com/blog-entry-319.html
/// <summary>
/// Addressable Assets にアドレス(キー)が存在するか否か?
/// </summary>
/// <param name="key">アドレス(キー)</param>
/// <returns>true = 存在する / false = 存在しない</returns>
public static bool Exists(string key)
{
return Addressables.LoadAsset<IResourceLocation>(key).Status == AsyncOperationStatus.Succeeded;
}
}

 やってみたら、どうやら存在しないキー(アドレス)を指定したときには「AsyncOperationStatus.Failed」になるみたいなので(まだ何もしてないときは None)、これだけで存在有無は確認できそうだ。Addressables.LoadAsset() の戻値:IAsyncOperation<IResourceLocation> は null は出ないみたいなので、チェックは省略した。

 使用例を簡単に書くと以下のようになる。

●Exists() の使用例(Addressable Assets からテクスチャを読み込んで RawImage に入れるサンプル)
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement;

public class AddressablesTest : MonoBehaviour { //※クラス名は任意

public RawImage image;

void Start () {
string key = "hogehoge"; //アセットのアドレス(キー) ※任意

if (AddressablesUtil.Exists(key))
{
Addressables.LoadAsset<Texture>(key).Completed += op => {
image.texture = op.Result;
};
}
else
{
Debug.LogWarning("Invalid key = " + key);
}
}
}




■Task と連携して非同期で読み込むメソッドを定義

 ついでに前述した「Exists」と以下の拡張メソッドを組み合わせて、動作を Task 化し、アドレスの存在検出して、有れば素材を、無ければ default(この場合は基本的に null)が返ってくる関数を定義してみよう。これを使うとかなり楽に非同期ロードができるようになる。Task 化メソッドは以下の記事(IAsyncOperationExtensions.GetAwaiter<T>)をまるっとコピーして欲しい。
 
【Unity】Addressable Asset Systemのリソース読み込みをasync/await対応する

●アドレス(キー)が存在するならロードし、無ければ default (= null) を返す
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement;
using System.Threading.Tasks; //※Task ならこっち
//using UniRx.Async; //※UniTask ならこっち

public static class AddressablesUtil { //※クラス名は任意

// (参考) https://qiita.com/toRisouP/items/e03b53e8eb6af06fbcbf (※IAsyncOperationExtensions.GetAwaiter<T> を利用)
// http://fantom1x.blog130.fc2.com/blog-entry-319.html
/// <summary>
/// アドレス(キー)が存在するならロードし、無ければ default (= null) を返す
/// </summary>
/// <typeparam name="T">素材の型</typeparam>
/// <param name="key">アドレス(キー)</param>
/// <returns>取得した素材</returns>
public static async Task<T> LoadOrDefault<T>(string key) where T : class
{
if (Exists(key)) //※前述のメソッド
return await Addressables.LoadAsset<T>(key); //※IAsyncOperationExtensions.GetAwaiter<T> が必要
return default;
}
}

 もちろん、Task を UniTask(UniRx が必要)に置き換えることも可能だ。

 簡単な使い方としては以下のようにしても良いだろう(※かなり適当なので、好きに書き換えて(笑))。

●LoadOrDefault()の使用例(Addressable Assets からテクスチャを読み込んで RawImage に入れるサンプル)
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement;
using System.Threading.Tasks; //※Task ならこっち
//using UniRx.Async; //※UniTask ならこっち

public class AddressablesTest : MonoBehaviour {

public RawImage image;

void Start () {
string key = "hogehoge"; //アセットのアドレス(キー) ※任意
LoadImage(key); //※Task なら警告が出る
//LoadImage(key).Forget(); //※UniTask なら警告を消せる
}

async Task LoadImage(string key)
{
var tex = await AddressablesUtil.LoadOrDefault<Texture>(key);
if (tex != null)
{
image.texture = tex;
}
else
{
Debug.LogWarning("Invalid key = " + key);
}
}

}

 もちろんこれも UniTask(UniRx が必要)に置き換えても良い。

 待機したいなら、async/await を付ければ良い(※この例ではあまり意味がないが(笑))。
async void Start () {
string key = "hogehoge"; //アセットのアドレス(キー) ※任意
await LoadImage(key);
}



 まだ、Addressable Assets はプレビュー版だからか、使い勝手が微妙な所もあるが、いずれは Resources の代わりに使われるようになるだろう。Addressable Assets ははじめから非同期ロードだったり、プリロード機能もあったりして便利なので、今から慣れておくのも良いかも知れない。





(関連記事)
【Unity】【C#】AssetBundleManager を WebGL で使う・エラー対処法
【Unity】【C#】アセットバンドルのキャッシュを調べて不要なものを消す


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  Unityトラブルシューティング  C# 
tb: 0   cm: --

【Unity】Android アプリでパーミッション(権限)要求をする  


 もしかしたら Unity2018.3.x のバグかも知れないが、Android ビルドした際に、パーミッション(権限)要求が自動で出なくなったので、「一部機能が使えない」等のトラブルが結構出てるのではないかと、改めて記事を書いてみた。

●Unity 上で権限要求ダイアログを出す

 公式のデベロッパーサイトにも掲載されているが、Android 8.0 ではパーミッション付与の仕様(どちらかというとガイドライン)も変更されたようで、今まではアプリ起動時に権限要求が自動で全て表示されてたけど、Android 8.0 以降では、その機能を利用するときに権限を要求しましょうということになっている(不要な権限やセキュリティに関わる権限をなるべく減らし、ユーザーにとって許可する内容をわかりやすくするため)。

Android 8.0 での動作変更点>パーミッション
実行時のパーミッション リクエスト

 冒頭に書いた「バグかも知れない」というのは、Unity でアプリビルドした際には自動で起動時に権限要求が表示されていたからだ。Android 開発では Android 6.0 以降、その機能は実装されているが、Unity のみ開発している人にとっては、割と敷居の高い設定項目となる(公式にも「上級レベルの Android 開発者にのみ推奨」とある)。そのうち、Android ビルドで権限要求の選択ができるようになるかも知れないが(例えば PlayerSettings>Other Settings>Configuration>Write Permission を「External (SD Card)」にすると、書き込み権限が自動的に要求される)、とりあえず現在の状態ではそういう機能はないので、プラグインを使って権限要求する方法を書いておこう。

 なお、今回の記事はプラグインのデモも兼ねている。サンプルシーンを使えば1から構築する必要はないので、すぐに試してみたい人はプラグインをダウンロードして欲しい。AssetStore版[無料]GoogleDrive版の2つがあるが、基本的にはどちらも同じものだ。好きな方で構わない。

>>GoogleDrive版 プラグイン&サンプルをダウンロード
(Google Drive を利用。画面右上にあるダウンロードアイコン:を押す)

>>AssetStore版をダウンロード


※とりあえず試してみたい方は、最新版をビルドした apk デモをダウンロードできます。動作確認にもどうぞ。

プラグインデモをダウンロード
(Google Drive を利用)


Android 4.2以上
※「提供元不明アプリのインストール」許可が必要です。


 プラグイン自体のセットアップは以前の記事を参照して欲しい(>>GoogleDrive版 | >>AssetStore版)。デモが入っているパスは GoogleDrive 版では「Assets/_Test/」に、AssetStore版は「Assets/FantomPlugin/Demo/」となっているので適宜置き換えて欲しい。ファイル名は基本的に同じだ。


(※) Unity 2018.3.1f1 / Windows10(x64) で確認



■PermissionCheckController を配置と使い方

 実際、以前の記事にも書いたのだが、パーミッション要求というのは通常ネイティブで書くしかないので、Unity で利用するには非常に困難である。なので、誰でも手軽に使えるようにプラグインとして導入しておいた(プラグインの中身も以前の記事とほぼ同じ)。その使い方を簡単に説明しておこう。

 「PermissionCheckController」はプレファブとスクリプトで提供している(FantomLib/Prefabs/System/ 以下 ※検索で探すと楽)。これをシーンにドラッグ&ドロップで配置しよう。配置したら、以下の項目を設定すれば完了だ。




※ここではデモシーン「ExternalStorageTest」の「AndroidFunctions」に入っているプレファブを使っているが、
基本的に同じものである(説明ダイアログ付きのデモとなっている)。

Permission要求する権限の文字列定数。この文字列定数は公式のデベロッパーサイトに掲載されている「Constant Value」の値を指定する。例えばカメラの権限なら「android.permission.CAMERA」を入れる。基本的に "android.permission.~" で始まる文字列となる。
Check On StartUnity のライフサイクル Start() で自動的にパーミッションのチェックが実行される。シーンごとに配置している場合は、プレファブがアクティブなら、シーン開始時にチェックされるようになる。
Request When Not Grantedパーミッションをチェックした際、許可されてないときのみ、要求ダイアログを出す(ユーザー設定により、出ないこともある)。オフのときは、要求ダイアログは出さず、結果のみコールバックへ返す。
Title, Message,
Style, Localize
権限要求の説明ダイアログのタイトル, メッセージ, スタイル, ローカライズの設定(多言語対応)。
Localize>Localize Resource が設定されてないときは、Title, Message で設定されている文字列のみが使われる(単一言語)。
On Resultパーミッションチェックの結果を Permission の文字列と真偽値(true = 許可 / false = 拒否)でコールバックする。複数同時に要求しているとき判別するのに使う。
On Grantedパーミッションチェックの結果が「許可(Granted)」のときのみコールバックする
On Deniedパーミッションチェックの結果が「拒否(Denied)」のときのみコールバックする
On Allowedパーミッションチェックの結果が「許可(Granted)」に変更されたときのみコールバックする(元から許可されてる、または拒否のときは何もしない)。

Manifest.permission(パーミッションの文字列定数)
テーマ(スタイル)
【Android】【Java】パーミッションの付与(許可)のチェックと要求をする



■AndroidManifest.xml を設定する

 意外と重要なのは、このマニフェストファイルの設定である。少なくとも以前はこの辺りを Unity が自動でやっていてくれたのだが、Unity2017 での Gradle ビルド導入以降、ある程度は自分で書いた方が良いこともある。例えばビルドエラー(or コンフリクト)が出たときなども、手動で直せることもある。

 最終的にマニフェストファイルは「Assets/Plugins/Android/AndroidManifest.xml」を中心に統合されたファイルとなる。そして、その設定に従ってアプリが起動する(なのでアプリが起動できないときは、このファイルを疑うのも良い)。

 ちなみにデフォルトのマニフェストテンプレートは Unity をインストールしたフォルダ以下の「Editor/Data/PlaybackEngines/AndroidPlayer/Apk/AndroidManifest.xml」にあり、
現在の出力されているマニフェストを見たいなら、ビルドした直後に、プロジェクト内の
Temp/StagingArea/AndroidManifest.xml」を開けば見ることができる。

Android マニフェスト
アプリ マニフェスト
UnityPlayerActivity Java コードの拡張

 ただ、資料を見ると結構難しく感じるので、ここではプラグインに付属している「AndroidManifest_test.xml」(AssetStore版は「_demo.xml」)を複製して「AndroidManifest.xml」にリネームし、これに少し手を加える形で使ってみよう。内容がわかるようになったら、自由に設定すれば良いのである。以前の記事にも書いたが、要点だけを抜き出すと「<manifest>~</manifest>」タグの間に「<uses-permission ~」で要求したい権限を書いておくことが必要となる。


 例えば、カメラマイクの権限を要求したいなら、以下のように書いておく(マニフェストに書いてない権限は要求できない)。

<manifest>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
・・・(略)・・・
</manifest>

パーミッションの設定
Manifest.permission(パーミッションの文字列定数)

 また、手動で要求する場合、「<application>~</application>」または「<activity>~</activity>」タグの間に、以下を入れて「自動で要求するのをスキップさせる」と良いとも公式に書いてある

<meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />

Android マニフェスト

 まぁ、この辺りは定形処理なので、そのうちまた自動化されそうだが(もしかしたら、この自動化あたりがバグっている?[Unity2018.3.1f1 時点])、最近は色々なプラグインを導入する機会も増えてきたので、覚えておくとイザというとき役に立つだろう。





(関連記事)
【Android】【Java】パーミッションの付与(許可)のチェックと要求をする
【Unity】Androidのトーストやダイアログ、通知、音声認識、ハード音量操作など基本的な機能を使えるプラグインを作ってみた
【Unity】AssetStore版 FantomPlugin のセットアップ
【Unity】Unity2018 でビルドエラー「CommandInvokationFailure: Gradle build failed.」が出る
【Unity】【C#】モバイルビルド中の警告:Game scripts or other custom code contains OnMouse_ event handlers.~ を消す


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  Unityトラブルシューティング 
tb: 0   cm: --

【Unity】【C#】UniRx で「1フレームごと待機して処理」してみる  


 今まで「VRM Live Viewer」では非同期処理に「Await Extensions」というライブラリを使っていたのだが、「UniRx」の非同期処理関連の資料を見てたら、同じようなことができるとわかったので、移行してみようと考えた。


 ただ注意しなくてはならないのは、「UniRx」の非同期処理は Scripting Runtime を「.NET 4.x」にすることと、C# 7.0が必須のため、現時点(Unity2018.2.x)では「Incremental Compiler」を導入する必要があるとのことだ。

UniTask - Unity + async/awaitの完全でハイパフォーマンスな統合

 「Incremental Compiler」はまだ Preview 版のため、商用利用は控えたほうが良いともあるね。まぁ、「VRM Live Viewer」はフリーなのと、色々実験してみたが、安定性にも問題無いようなので(どちらかというと Stable[安定版] が出たら、メソッドの仕様(引数など)が変わって、書き換え必須となることの方が辛いね(笑))、導入を試みてみたら成功した。実際「UniRx」上で使える「UniTask」は「async/await/Task」の上位互換で、現在私が使っている機能もそのまま移行できたので、基本的な使い方ならば、バージョンアップ・正式版が出てもそれほど問題ないとも思う。また今後UI処理などを少し強化したいしね。そういったものは「UniRx」はとても強い(笑)。

 なので、現時点では導入する人は少ないかも知れないが、「.NET 4.x」の非同期処理「async/await/Task」が使えるようになると、コルーチンで書いていたものがスッキリする上に、別スレッドでバックグラウンド処理などもとても簡単になるので、いずれは誰しも使うようになるだろう(笑)。そしてはじめに使いたいのはやはり、今までコルーチンなどで書いていた「1フレーム待機して処理」かなと(笑)。ググったらなぜかあまりハッキリとした答えがなかったので、ちょっと実験してみた感じ。たぶん他の人も同じことを調べるだろうしね。

■「1フレームごとに待機して処理」っぽくなりそうなものを、色々やってみる
 ●コルーチンで1フレームごとに待機して処理(ログ出力のみ。動作の基準)
 ●Observable.NextFrame() で1フレームごとに待機して処理?
 ●Observable.TimerFrame() で1フレームごとに待機して処理(引数=1)?
 ●Observable.TimerFrame() で1フレームごとに待機して処理(引数=0)?
 ●Observable.ReturnUnit().DelayFrame() で1フレームごとに待機して処理(引数=1)?
 ●Observable.ReturnUnit().DelayFrame() で1フレームごとに待機して処理(引数=0)?
 ●UniTask.DelayFrame() で1フレームごとに待機して処理?
 ●UniTask.WaitUntil() で1フレームごとに待機して処理?
 ●UniTask.Yield() で1フレームごとに待機して処理?
■とりあえず「1フレームごとに待機して処理」を簡単に(実験してみた結果)

(※) Unity 2018.2.1f1 / UniRx 6.2.2 / Incremental Complier 0.0.42(Preview) / Windows10(x64) で確認



■「1フレームごとに待機して処理」っぽくなりそうなものを、色々やってみる

 まずは今まで通り、コルーチンで「1フレームごとに待機して処理」をしてみる。処理自体はただのログ出力なので、実際の処理の重さは考慮に入れてない。ちなみに「Await Extensions」では「yield return new WaitForEndOfFrame()」の代わりに「await new WaitForEndOfFrame()」が使える(そしてメソッドに async が使えるので非同期処理が簡潔に書ける。StartCoroutine() もいらない)。


●コルーチンで1フレームごとに待機して処理(ログ出力のみ。動作の基準)
using System.Collections;
using UnityEngine;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

StartCoroutine(WaitForEndOfFrameCoroutineTest());
}

IEnumerator WaitForEndOfFrameCoroutineTest()
{
Debug.Log("frame : " + Time.frameCount); //1
yield return new WaitForEndOfFrame();
Debug.Log("frame : " + Time.frameCount); //2
yield return new WaitForEndOfFrame();
Debug.Log("frame : " + Time.frameCount); //3
yield return new WaitForEndOfFrame();
Debug.Log("frame : " + Time.frameCount); //4
yield return new WaitForEndOfFrame();
Debug.Log("frame : " + Time.frameCount); //5
}
}

(Start) frame : 1
frame : 1
frame : 2
frame : 3
frame : 4
frame : 5

 なんてことはないコルーチンで順次処理していくときのようなコード(本来は同じ処理ならループにするだろうが、これはあくまでサンプルなので、実際には別々の処理が入ると考えて欲しい)。これを基準として「UniRx」でオペレータなども使って色々試してみよう。なお、ここでは「yield return new WaitForEndOfFrame()」を使っているが、実行タイミングを気にしないなら「yield return null」でも良い。

イベント関数の実行順



●Observable.NextFrame() で1フレームごとに待機して処理?

using System.Collections;
using UnityEngine;
using UniRx;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

ObservableNextFrameTest();
}

async void ObservableNextFrameTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await Observable.NextFrame();
Debug.Log("frame : " + Time.frameCount); //3
await Observable.NextFrame();
Debug.Log("frame : " + Time.frameCount); //5
await Observable.NextFrame();
Debug.Log("frame : " + Time.frameCount); //7
await Observable.NextFrame();
Debug.Log("frame : " + Time.frameCount); //9
}
}

(Start) frame : 1
frame : 1
frame : 3
frame : 5
frame : 7
frame : 9

 UniRx で一番始めに目についたのは「Observable.NextFrame()」だったが、どうやら await で1フレーム、NextFrame() で1フレーム待機のようだ。フレームが1つ飛びになっていた。期待していた処理とは違う。



●Observable.TimerFrame() で1フレームごとに待機して処理(引数=1)?

using System.Collections;
using UnityEngine;
using UniRx;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

ObservableTimerFrameTest();
}

async void ObservableTimerFrameTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await Observable.TimerFrame(1);
Debug.Log("frame : " + Time.frameCount); //3
await Observable.TimerFrame(1);
Debug.Log("frame : " + Time.frameCount); //5
await Observable.TimerFrame(1);
Debug.Log("frame : " + Time.frameCount); //7
await Observable.TimerFrame(1);
Debug.Log("frame : " + Time.frameCount); //9
}
}

(Start) frame : 1
frame : 1
frame : 3
frame : 5
frame : 7
frame : 9

 Observable.NextFrame() と結果は同じになった。ではちょっと引数を0にしてみようと実験してみると…


●Observable.TimerFrame() で1フレームごとに待機して処理(引数=0)

using System.Collections;
using UnityEngine;
using UniRx;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

ObservableTimerFrameTest();
}

async void ObservableTimerFrameTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await Observable.TimerFrame(0);
Debug.Log("frame : " + Time.frameCount); //2
await Observable.TimerFrame(0);
Debug.Log("frame : " + Time.frameCount); //3
await Observable.TimerFrame(0);
Debug.Log("frame : " + Time.frameCount); //4
await Observable.TimerFrame(0);
Debug.Log("frame : " + Time.frameCount); //5
}
}

(Start) frame : 1
frame : 1
frame : 2
frame : 3
frame : 4
frame : 5

 いけた(笑)。これは期待していた処理と合致する。



●Observable.ReturnUnit().DelayFrame() で1フレームごとに待機して処理(引数=1)?

using System.Collections;
using UnityEngine;
using UniRx;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

ObservableDelayFrameTest();
}

async void ObservableDelayFrameTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await Observable.ReturnUnit().DelayFrame(1);
Debug.Log("frame : " + Time.frameCount); //3
await Observable.ReturnUnit().DelayFrame(1);
Debug.Log("frame : " + Time.frameCount); //5
await Observable.ReturnUnit().DelayFrame(1);
Debug.Log("frame : " + Time.frameCount); //7
await Observable.ReturnUnit().DelayFrame(1);
Debug.Log("frame : " + Time.frameCount); //9
}
}

(Start) frame : 1
frame : 1
frame : 3
frame : 5
frame : 7
frame : 9

 これも Observable.NextFrame() と結果は同じになった。では引数を0にしてみると…


●Observable.ReturnUnit().DelayFrame() で1フレームごとに待機して処理(引数=0)

using System.Collections;
using UnityEngine;
using UniRx;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

ObservableDelayFrameTest();
}

async void ObservableDelayFrameTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await Observable.ReturnUnit().DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //2
await Observable.ReturnUnit().DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //3
await Observable.ReturnUnit().DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //4
await Observable.ReturnUnit().DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //5
}
}

(Start) frame : 1
frame : 1
frame : 2
frame : 3
frame : 4
frame : 5

 これもいけた。これは期待していた処理と合致する。実装自体は Observable.TimerFrame() とは違うみたいだけどね。



●UniTask.DelayFrame() で1フレームごとに待機して処理?

using System.Collections;
using UnityEngine;
using UniRx;
using UniRx.Async;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

UniTaskDelayFrameTest();
}

async void UniTaskDelayFrameTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await UniTask.DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //1
await UniTask.DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //2
await UniTask.DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //3
await UniTask.DelayFrame(0);
Debug.Log("frame : " + Time.frameCount); //4
}
}

(Start) frame : 1
frame : 1
frame : 1
frame : 2
frame : 3
frame : 4

 せっかくのなで、UniTask も試してみた。するとあれ?なぜか初めの1回は待機されてない。なんか理由があるのだろうけど、今回は放おっておこう(←誰か調べて(笑))。こちらは「using UniRx.Async;」が必要。



●UniTask.WaitUntil() で1フレームごとに待機して処理?

using System.Collections;
using UnityEngine;
using UniRx;
using UniRx.Async;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

UniTaskWaitUntilTest();
}

async void UniTaskWaitUntilTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await UniTask.WaitUntil(() => true);
Debug.Log("frame : " + Time.frameCount); //1
await UniTask.WaitUntil(() => true);
Debug.Log("frame : " + Time.frameCount); //2
await UniTask.WaitUntil(() => true);
Debug.Log("frame : " + Time.frameCount); //3
await UniTask.WaitUntil(() => true);
Debug.Log("frame : " + Time.frameCount); //4
}
}

(Start) frame : 1
frame : 1
frame : 1
frame : 2
frame : 3
frame : 4

 UniTask.DelayFrame() と同じ結果になった。これもはじめの1回が待機されてない…?ま、まぁ、今回はコルーチンでの表記をそのまま代替できる書き方を探していただけなので(パフォーマンスなども考慮に入れてない)、これで勘弁してやろう(笑)。誰かそのうち調べてくれるだろうと期待してる(←投げっぱなし(笑))。



●UniTask.Yield() で1フレームごとに待機して処理?

using System.Collections;
using UnityEngine;
using UniRx;
using UniRx.Async;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

UniTaskYieldTest();
}

async void UniTaskYieldTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await UniTask.Yield();
Debug.Log("frame : " + Time.frameCount); //1
await UniTask.Yield();
Debug.Log("frame : " + Time.frameCount); //2
await UniTask.Yield();
Debug.Log("frame : " + Time.frameCount); //3
await UniTask.Yield();
Debug.Log("frame : " + Time.frameCount); //4
}
}

(Start) frame : 1
frame : 1
frame : 1
frame : 2
frame : 3
frame : 4

 これも UniTask.DelayFrame() と同じ結果になった。これもはじめの1回が待機されてない?
 本来はメインスレッドに切り替えたりPlayerLoopに同期したりするのに使うみたいだが、次のフレームになるので、近い動作になるっぽい。



■とりあえず「1フレームごとに待機して処理」を簡単に(実験してみた結果)

 他にも色々実験している記事もあったが、単純に面倒なので、とりあえず「Observable.TimerFrame(0)」または「Observable.ReturnUnit().DelayFrame(0)」を代替として使うことにしてみた。もしからしたら正しい使い方ではないかも知れないので、static な関数(WaitForFrame)にしておいて、後で書き換えられるようにしておけば、修正も楽かも知れない。「Observable.TimerFrame(0)」は UniRx 特有の「マイクロコルーチン」というものを使っているので(負荷が軽いらしい)、今回はこちらで書いておこう。まぁ、戻値の型の問題はあるが、あくまでコルーチンでの「yield return null」みたいな使い方を想定しているので、今回は気にしないとする(笑)。Observable のように拡張メソッドにしても良いと思うけど(「Observable_Joins.cs」に書くとか)、その辺はご自由に。

●とりあえず1フレームごとに待機して処理
using System.Collections;
using UnityEngine;
using UniRx;
using UniRx.Async;

public class EachFrameTest : MonoBehaviour {

// Use this for initialization
void Start () {
Debug.Log("(Start) frame : " + Time.frameCount); //1

WaitForFrameTest();
}

async void WaitForFrameTest()
{
Debug.Log("frame : " + Time.frameCount); //1
await WaitForFrame();
Debug.Log("frame : " + Time.frameCount); //2
await WaitForFrame();
Debug.Log("frame : " + Time.frameCount); //3
await WaitForFrame();
Debug.Log("frame : " + Time.frameCount); //4
await WaitForFrame();
Debug.Log("frame : " + Time.frameCount); //5
}

public static IObservable<long> WaitForFrame()
{
return Observable.TimerFrame(0);
}
}

(Start) frame : 1
frame : 1
frame : 2
frame : 3
frame : 4
frame : 5

 「Observable.TimerFrame()」だと戻値の型が long となっているので、Unit にしたいなら、以下のようにしても良いかも知れない(まぁ、今回の使い方のように、捨て値なら無駄な処理が増えるだけだが…)。

●戻値を Unit に変えた例
public static IObservable<Unit> WaitForFrame()
{
return Observable.TimerFrame(0).AsUnitObservable();
}


 こちらの記事にも書いてあるけど「コルーチンをほぼ駆逐」できると表記がすっきりし、しかもコルーチンと違って、別スレッド動作や非アクティブなオブジェクトでも動くので(というより、MonoBehaviour に依存しないで動作できるので)、いずれは「async/await」的な書き方の方が主流になるかもね。「VRM Live Viewer」は商用アプリにするつもりは無いし、実験的にやってみることに意義があると思ってるので(ファイルドロップ→非同期なファイル読み込みは既に実装されてる)、前のめりで新しい機能を導入していきたい(笑)。

(参考)
Unity UniRxとasync/awaitでフレーム管理
UniRx.Async機能紹介
UniTask - Unity + async/awaitの完全でハイパフォーマンスな統合/a>
Unityにおけるコルーチンの省メモリと高速化について、或いはUniRx 5.3.0でのその反映





(関連記事)
【Unity】VRM(VRoid)をライブステージで踊らせるアプリを作ってみた


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityオープンソースライブラリ  Unityライブラリ  実証実験 
tb: 0   cm: --


プロフィール

Social

検索フォーム

全記事一覧

カテゴリ

ユーザータグ

最新記事

リンク

PR

▲ Pagetop