yotiky Tech Blog

とあるエンジニアの備忘録

Azure Functions で Azure Table Storage を操作する

目次

検証環境

  • Azure Functions v3
  • Microsoft.Azure.Cosmos.Table v1.0.8

古いライブラリに注意

  • WindowsAzure.Storage は非推奨
  • Microsoft.Azure.CosmosDB.Table はまもなく非推奨

実装

NuGet でライブラリをインストールする。

using Microsoft.Azure.Cosmos.Table;

データ定義は、TableEntity を継承したクラスを作成する。 取得時にパラメータなしのコンストラクタが必要になる。

public class CustomerEntity : TableEntity
{
    public CustomerEntity() { }
    public CustomerEntity(string lastName, string firstName)
    {
        PartitionKey = lastName;
        RowKey = firstName;
    }

    public string Email { get; set; }
    public string PhoneNumber { get; set; }
}

CloudTableインスタンスを取得する。

var connectionString = "ConnectionString";
var tableName = "TableA";

var storageAccount = CloudStorageAccount.Parse(connectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var cloudTalbe = tableClient.GetTableReference(tableName);

// なければ作成する
await cloudTalbe.CreateIfNotExistsAsync();

挿入

1件挿入する場合。

var entity = new CustomerEntity("Tanaka", "Taro")
{ 
    Email = "aaa", 
    PhoneNumber = "001"
};

await cloudTalbe.ExecuteAsync(TableOperation.InsertOrReplace(entity));

まとめて挿入する場合。

var entities = new[]
{
    new CustomerEntity("Tanaka", "Taro"){ Email = "aaa", PhoneNumber = "001"},
    new CustomerEntity("Tanaka", "Jiro"){ Email = "bbb", PhoneNumber = "002"},
};

var operation = new TableBatchOperation();
foreach (var entity in entities)
{
    operation.Add(TableOperation.InsertOrReplace(entity));
}

await cloudTalbe.ExecuteBatchAsync(operation);

取得

PartitionKeyとRowKeyを指定して1件取得する場合。

var entity = new CustomerEntity("Tanaka", "Taro")
{ 
    Email = "aaa", 
    PhoneNumber = "001"
};

var tableResult = await cloudTalbe.ExecuteAsync(TableOperation.Retrieve<CustomerEntity>(entity.PartitionKey, entity.RowKey));
var result = tableResult.Result as CustomerEntity;

クエリを使う場合。

var entity = new CustomerEntity("Tanaka", "Taro")
{ 
    Email = "aaa", 
    PhoneNumber = "001"
};

var query = new TableQuery<CustomerEntity>();
query.FilterString =
    TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition(nameof(CustomerEntity.PartitionKey), QueryComparisons.Equal, entity.PartitionKey),
        TableOperators.And,
        TableQuery.GenerateFilterCondition(nameof(CustomerEntity.RowKey), QueryComparisons.Equal, entity.RowKey)
    );

var tableResult = await cloudTalbe.ExecuteQuerySegmentedAsync(query, null);

var result = tableResult.Results.SingleOrDefault();

大量に取得する場合。

var entity = new CustomerEntity("Tanaka", "Taro")
{ 
    Email = "aaa", 
    PhoneNumber = "001"
};

var query = new TableQuery<CustomerEntity>();
query.FilterString = 
    TableQuery.GenerateFilterCondition(nameof(CustomerEntity.PartitionKey), QueryComparisons.Equal, entity.PartitionKey);

var list = new List<CustomerEntity>();

TableContinuationToken token = null;
do
{
    var segment = await cloudTalbe.ExecuteQuerySegmentedAsync(query, token);
    list.AddRange(segment.Results);
    token = segment.ContinuationToken;
} while (token != null);

参考:1 件のクエリで大量のエンティティを取得する

削除

取得時の Entity が書き換わってないかチェックして削除する場合。

await cloudTalbe.ExecuteAsync(TableOperation.Delete(entity));

気にせず削除する場合は ETag にワイルドカードを使う。

var entity = new CustomerEntity("Tanaka", "Taro");
entity.ETag = "*";
await cloudTalbe.ExecuteAsync(TableOperation.Delete(entity));

参考

関連記事

Azure Functions で Azure Blob Storage にファイルを保存する

目次

検証環境

  • Azure Functions v3
  • Azure.Storage.Blobs v12.8.0

実装

Azure Functions のプロジェクトに NuGet で「Azure.Storage.Blobs」をインストールする。

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
private static string connectionString = "YOUR CONNECTION STRING";
private static string blobContainerName = "CONTAINER_NAME";
private static string blobName = "BLOB_NAME";

private static async Task Upload(HttpRequest req)
{
    var blobServiceClient = new BlobServiceClient(connectionString);
    var blobContainerClient = blobServiceClient.GetBlobContainerClient(blobContainerName );
    await blobContainerClient.CreateIfNotExistsAsync(PublicAccessType.None);
    var blobClient = blobContainerClient.GetBlobClient(blobName);
    
    await blobClient.UploadAsync(req.Body);
}

関連記事

Azure Functions で Shared Access Signatures (SAS) を発行する

目次

検証環境

  • Azure Functions v3
  • Azure Storage Blobs v12.8.0

概要

Shared Access Signatures (SAS) は、リソースへのアクセス権に制限を付けてトークンを生成し、Shared Access Signatures URI を使ってリソースへのアクセスを許可する。

SASの種類は3種類。

  • ユーザー委任 SAS
  • サービス SAS
  • アカウント SAS

制御できる項目。

  • アクセスできるリソース
  • 書き込み、読み取りなどのアクセス許可
  • トークンの有効期限
  • IP制限

生成するには、ポータルや Microsoft Azure Storage Explorer を使う方法、プログラムで生成するなどがある。

ここではサービス SAS をプログラムで発行するサンプルとなる。 また、保存されているアクセス ポリシーにサービス SAS が関連付けられないため、強制的に失効させることができないので取り扱いは注意が必要。 有効期限を 1 時間以下に設定することが推奨されている。

docs.microsoft.com

実装

コンテナの SAS トーク

private static string _connectionString  = "connectionString";
private static string _container = "container";

[FunctionName("GetAccessToken")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    var (url, token) = await GetContainerSasToken();
    log.LogInformation(url + token);

    return new OkObjectResult(token);
}

public static async Task<(string, string)> GetContainerSasToken()
{
    var account = CloudStorageAccount.Parse(_connectionString);
    var client = account.CreateCloudBlobClient();
    var container = client.GetContainerReference(_container);

    await container.CreateIfNotExistsAsync();

    var policy = new SharedAccessBlobPolicy()
    {
        SharedAccessStartTime = DateTime.UtcNow.AddMinutes(0),
        SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(3),
        Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.List,
    };

    var token = container.GetSharedAccessSignature(policy, null, null, null);

    // IP制限
    //var ipRange = new IPAddressOrRange("168.1.5.65");
    //var ipRange = new IPAddressOrRange("168.1.5.60", "168.1.5.70");
    //var token = container.GetSharedAccessSignature(policy, null, null, ipRange);

    return (container.Uri.AbsoluteUri, token);
}

ログに出力される URI:%3Aエンコードされているので : に戻すとそのままの URI でブラウザでアクセスできる。

Blob の SAS トーク

private static string _connectionString  = "connectionString";
private static string _container = "container";
private static string _blobName = "blobName";

[FunctionName("GetAccessToken")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    var (url, token) = await GetBlobSasToken();
    log.LogInformation(url + token);

    return new OkObjectResult(token);
}
public static async Task<(string, string)> GetBlobSasToken()
{
    var account = CloudStorageAccount.Parse(_connectionString);
    var client = account.CreateCloudBlobClient();
    var container = client.GetContainerReference(_container);

    await container.CreateIfNotExistsAsync();

    var blob = container.GetBlockBlobReference(_blobName);

    var exists = await blob.ExistsAsync();
    if (!exists) { return (null, null); }

    var policy = new SharedAccessBlobPolicy()
    {
        SharedAccessStartTime = DateTime.UtcNow.AddMinutes(0),
        SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(3),
        Permissions = SharedAccessBlobPermissions.Read,
    };

    var token = blob.GetSharedAccessSignature(policy, null, null, null, null);

    // IP制限
    //var ipRange = new IPAddressOrRange("168.1.5.65");
    //var ipRange = new IPAddressOrRange("168.1.5.60", "168.1.5.70");
    //var token = blob.GetSharedAccessSignature(policy, null, null, null, ipRange);

    return (blob.Uri.AbsoluteUri, token);
}

ログに出力される URI:%3Aエンコードされているので : に戻すとそのままの URI でブラウザでアクセスできる。

SAS トークンの生成と同時に、受け取ったパラメータに応じて Blob のパスをサーバー側で決めることもできるため、SAS URI 自体を返すようにするとクライアントは Blob や対象のファイルなど詳細を知らなくてよくなる。

参考

関連記事

Unity - Addressable Assets System

目次

検証環境

  • Unity 2019.4.3f1
  • Addressables 1.8.5

概要

Addressable Asset System は、Prefab や Scene、Texture、Shader、Material、TextMeshPro の FontAsset などのリソースに付与したアドレス(Addresable Name)を使ってアセットを管理するシステム。
従来からある AssetBundle で、各自実装しなければならなかった「管理する仕組み」などを提供している。
ローカルから生のアセットやビルドしたAssetBundle、リモートに配置した AssetBundle など、読み込む配置先を設定で切り替えることで、統一的なインターフェースでアセットを扱うことができるようになる。

以前は Resources フォルダを使ったアセットの読み込みがあった。 こちらはメタ情報を付与したり、圧縮したり、リソースは変換されてアプリに埋め込まれる。現在は非推奨な方法。

StreamingAssets フォルダを使ってもアセットを読み込める。 こちらは余計な変換処理は挟まれないが、Resources フォルダと同様にアプリに埋め込まれる。

AssetDatabase はプロジェクトフォルダ内にあるアセットならどれでも読み込めるが UnityEditor でしか動かない。

AssetBundle は、予めアセットをプラットフォームごとにビルドして、サーバーや StreamingAssets フォルダに配置することで、ロードできるようになる。 外部に配置するためアプリの容量は小さくなるが、ビルドや管理に関する機能が提供されておらず各自で行う必要があり手間がかかってしまう。

インストール

Window > Package Manager を開いて、[Addressables] からインストールする。

アセットを読み込む

AssetDatabase を使って UnityEditor で読み込む

対象となるアセットの Inspector を開いて、[Addresable] にチェックを付ける。

f:id:yotiky:20210204225106p:plain

もしくは、Window > Asset Management > Addresables > Groups を開いて、対象のアセットをドラッグ&ドロップする。

f:id:yotiky:20210204225406p:plain

スクリプトからは Addresable Name を用いてアセットをロードするため、任意の名前をつける。

Play Mode Scripts で [Use Asset Database (faster)] を選択すると、実行時に AssetDatabase を使って直接アセットを読み込むことができる。

f:id:yotiky:20210205002320p:plain

続いてスクリプトから読み込む方法。

public AssetReference assetReference;

async Task Start()
{
    var m = await Addressables.LoadAssetAsync<Material>("Assets/Materials/PulseCopy.mat").Task;

    var go = await Addressables.LoadAssetAsync<GameObject>("Assets/Prefabs/Sphere.prefab");
    var o1 = Instantiate(go);

    var o2 = await Addressables.InstantiateAsync("Assets/Prefabs/Sphere.prefab");
    o2.GetComponent<Renderer>().material = m;

    var o3 = await assetReference.InstantiateAsync();
}
void OnDestroy()
{
    if (m != null)
        Addressables.Release(m);
    if (go != null)
        Addressables.Release(go);
    if (o2 != null)
        Addressables.ReleaseInstance(o2);
    if (o3 != null)
        Addressables.ReleaseInstance(o3);
}

アセットのロードは、リリースと対となる。Addressables は内部で参照カウントによって管理しているため、GameObject を単に Destroy すると不整合が起きる。Addressables からロードしたアセットは必ずアンロードする。(手動自動は問わない)*1

アセットを読み込むには、アドレス(Addresable Name)を指定して Addressables.LoadAssetAsync() を呼び出す。開放する時は、ロードの戻り値として受け取ったオブジェクトを Release() に渡す。

GameObject を生成するには2パターンある。
1つ目は Addressables.LoadAssetAsync() してから Instantiate() する方法。 こちらは前述した通りRelease() でリリースする。
2つ目は直接 Addressables.InstantiateAsync() する方法。 こちらは ReleaseInstance()GameObject を渡す。

AssetReference を使うと Inspector 上から Addressable に登録したアセットを設定できる。

ロード処理は非同期のため、戻り値である AsyncOperationHandleTask プロパティを await する。
UnitTask を使った場合は、InstantiateAsync() を直接 await できる。

Play Mode Script

  • Use Asset Database (faster)
  • Simulate Groups (advanced)
  • Use Existing Build (requires built groups)

[Use Asset Database] は、AssetDatabase を使って直接アセットをロードする。アセットのビルドが不要ですぐに実行できるが、 Addressables Event Viewer には AssetBundle の情報は表示されない。

f:id:yotiky:20210205014348p:plain:w250

[Simulate Groups] は、AssetDatabase を使ってロードされるためアセットのビルドは不要だが、AssetBundle の依存関係を分析してシュミレーションすることができる。

f:id:yotiky:20210205014523p:plain

[Use Existing Build] は、実際にビルドした AssetBundle をロードする。

f:id:yotiky:20210205014744p:plain

AssetBundle をローカルから読み込む

Play Mode Script で [Use Existing Build] を選択する。

f:id:yotiky:20210204225628p:plain:w250

Addressables Groups ウィンドウでグループを選択すると、Inspector に設定が表示される。 Build Path と Load Path がどちらも Local になっていることを確認する。

f:id:yotiky:20210205020457p:plain:w350

Addressables Groups ウィンドウで New Build > Default Build Script を実行するとビルドが実行され AssetBundle が生成される。

f:id:yotiky:20210204225709p:plain

スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。

AssetBundle をリモート(ローカルサーバー)から読み込む

Addressables Groups ウィンドウで Tools > Hosting Services を開いて、[Local Hosting] を作成する。

f:id:yotiky:20210205023109p:plain:w200 f:id:yotiky:20210205023147p:plain:w200

任意の [Service Name] を入力し、[Enable] にチェックを付けるとポートが割り当てられる。

f:id:yotiky:20210205023313p:plain

続いて、Addressables Group ウィンドウで Profile > Manage Profiles で Addressables Profile ウィンドウを開いて、[Profile] を作成する。

f:id:yotiky:20210205023528p:plain:w200 f:id:yotiky:20210205023712p:plain

f:id:yotiky:20210205023744p:plain

任意の [Profile Name] を付けて、RemoteBuildPath に [LocalHostData/[BuildTarget]]、RemoteLoadPath に [http://[PrivateIpAddress]:[HostingServicePort]] と入力する。

f:id:yotiky:20210205025746p:plain

Addressables Group ウィンドウで Profile から作成したプロファイルを選択する。

f:id:yotiky:20210205025720p:plain:w200

Addressables Group ウィンドウでグループを選択し、 Inspector 上で、 Build Path と Load Path に Remote を選択する。それぞれプロファイルで設定した内容が反映される。

f:id:yotiky:20210205030004p:plain:w350
f:id:yotiky:20210205020607p:plain:w250

同 Inspector から [Inspect Top Level Settings] をクリックして、AddressableAssetSettings を開く。 [Build Remote Catalog] にチェックを付け、Build Path と Load Path に Remote を選択する。

f:id:yotiky:20210205034729p:plain

[Player Version Override] は指定しないとビルド日時で名前が生成されるため、ビルドするたびに新しいファイルが生成される。同じバージョン番号を指定しているうちはコンテンツカタログが上書き更新されるのでファイルが増え続けることはない。

f:id:yotiky:20210205035737p:plain

Play Mode Script で [Use Existing Build] を選択する。

f:id:yotiky:20210204225628p:plain:w250

アセットをビルドすると、プロジェクト直下に LocalHostData フォルダが作成され Catalog と AssetBundle が出力される。

f:id:yotiky:20210205032453p:plain:w200

スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。

AssetBundle をリモート(Azure Blob Storage)から読み込む

今回は、Azure Blob Storage にパブリックアクセスレベルを「BLOB」にした assets という名前でコンテナを用意する。(危険なので使い終わったらさっさと除去しすること) 実際は SAS などを生成してセキリティに気をつける必要がある。

コンテナのプロパティから URL をメモしておく。

f:id:yotiky:20210205102202p:plain


Addressables Groups ウィンドウで Create > Group > Packed Assets を選択して、グループを作成する。

f:id:yotiky:20210205052315p:plain

任意のアドレス(Addressable Name)を付け、対象のアセットを登録する。

f:id:yotiky:20210205052715p:plain

Group を選択して Inspector を開いて、Build Path と Load Path に Remote を選択する。

f:id:yotiky:20210205053117p:plain:w300

同 Inspector から Inspect Top Level Settings をクリックして、AddressableAssetSettings を開く。 [Build Remote Catalog] にチェックを付け、Build Path と Load Path に Remote を選択する。

f:id:yotiky:20210205034729p:plain

Addressables Group ウィンドウで Profile > Manage Profiles で Addressables Profile ウィンドウを開いて、[Profile] を作成する。

f:id:yotiky:20210205023528p:plain:w200 f:id:yotiky:20210205023712p:plain

任意の [Profile Name] を付けて、RemoteLoadPath に用意した Blob の URL を使ってhttps://xxx.blob.core.windows.net/assets/[BuildTarget] と設定する。[BuildTarget] はビルドしたときのプラットフォームの文字列が入る。

f:id:yotiky:20210205023744p:plain

Addressables Group ウィンドウで Profile から作成したプロファイルを選択する。

f:id:yotiky:20210205101418p:plain:w200

Play Mode Script で [Use Existing Build] を選択する。

f:id:yotiky:20210204225628p:plain:w250

アセットをビルドすると、プロジェクト直下に ServerData フォルダが作成され Catalog と AssetBundle が出力される。

f:id:yotiky:20210205101550p:plain f:id:yotiky:20210205101746p:plain:w300

作成した Catalog と AssetBundle を、Azure Blob Storage にアップロードする。 コンテナのパスなど RemoteLoadPath が変わった場合は再度アセットをビルドしてアップロードし直す。

f:id:yotiky:20210205102437p:plain

スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。

グループ

フォルダをドラッグ&ドロップすることもできる。階層を維持する場合、フォルダに対してアドレス(Addresable Name)とラベルを設定できる。フォルダを指定してまとめて読み込むことはできなそう。 ラベルは配下のアセットにも適用されるので問題なくまとめて読み込むことができる。 アドレス(Addresable Name)もラベルも配下のアセットに個別に設定することはできないが、決められたアドレス(Addresable Name)を使えば個別に読み込むことは可能である。

f:id:yotiky:20210205132108p:plain

フォルダが不要な場合はグループの直下に移動する。

f:id:yotiky:20210205132229p:plain

グループとプロファイルの関係

Addressables Groups

グループでは、アセットにアドレス(Addresable Name)とラベルを付与して、グループに登録する。 アセットはいずれかのグループに所属し、複数のグループには登録できない。

f:id:yotiky:20210205152731p:plain

グループの設定

グループ単位で AssetBundle 化されるため、グループの設定では、Build Path と Load Path が選択できたり、圧縮やプロバイダーの選択など、Packing や Loading などの設定ができる。

f:id:yotiky:20210205153754p:plain:w300

AddressableAssetSettings

Assets/AddressableAssetsData/AddressableAssetSettings では、コンテンツカタログの設定ができる。コンテンツカタログを出力するかどうか、Build Path と Load Path をどこにするか、バージョンを上書きするかなどの設定が含まれる。

f:id:yotiky:20210205154027p:plain:w350

Addressables Profiles

プロファイルでは、ビルドとロードのパスをどこにするかの設定を作り分けられるようになっている。Unity Editor で実行する時、ローカルホストや開発サーバー、本番サーバーで実行する時などプロファイルを切り替えるだけでビルド先とロード先を変更できるようになる。

f:id:yotiky:20210205153238p:plain

ビルド

各グループはグループ毎に Build Path / Load Path の設定を持つ。
コンテンツカタログも別途 Build Path / Load Path の設定を持ち、出力するように設定にする必要がある。
これらの Path は、選択した Profile によって値が切り替えられる。

ビルドしたけど出力されていないと思った時は各グループ、コンテンツカタログの Build Path がどこになっているかを確認すると良い。

ラベル

アセットにラベルを付けてグルーピングすることができる。アセットでラベルを指定してまとめて読み込んだり、ダウンロードすることができる。

Addressables Groups ウィンドウで、Tools > Labels を開いて、ラベルを作成する。

f:id:yotiky:20210205125644p:plain:w200 f:id:yotiky:20210205125806p:plain:w250

Addressables Groups ウィンドウで任意のアセットにラベルを設定する。

f:id:yotiky:20210205130021p:plain:w250

Addressables.LoadAssetsAsync() の引数にラベルを渡すとまとめてロードできる。(Assets が複数形なので注意)

var objects = await Addressables.LoadAssetsAsync<Object>("Model", null);
foreach(var item in objects)
{
    Debug.Log(item);
}

f:id:yotiky:20210205130747p:plain:w300

コンテンツカタログ

ビルド結果のファイル構成は以下の通り。

f:id:yotiky:20210205155523p:plain:w300

  • catalog_<version>.hash
  • catalog_<version>.json
  • グループ毎の AssetBundle や依存関係のある AssetBundle など

hash ファイルは、コンテンツカタログの更新の有無を比較するために使われる。 hash ファイルに違いがある場合、リモートから json ファイルをロードする。 hash ファイルが同じ場合はローカルのキャッシュからロードされる。

これら hash ファイルと json ファイルの取得元やファイル名などの情報は、アプリのビルド時に settings.json を書き出され StreamingAssets に保存される。*2 アプリのビルドをしないと取得元を変更することができないため、[Player Version Override] はアプリのバージョンと同じにして、アプリが同じバージョンの間はコンテンツカタログの名前が変わらないようにして上書き更新し続けるのが良さそうである。

light11.hatenadiary.com

Bundle Mode (Packing Mode)

AssetBundle の分割単位を設定できる。 原則としてグループ毎に AssetBundle に分割され、グループ内であっても Scene とそれ以外では更に分割される。

f:id:yotiky:20210205162021p:plain:w300

  • Pack Together
  • Pack Separately
  • Pack Together By Label


Pack Together はグループ単位でまとめられる。同じグループでも Scene は別になる。

f:id:yotiky:20210205162829p:plain:w300

Pack Separately はアドレス単位ですべて個別に分割される。

f:id:yotiky:20210205163357p:plain

Pack Together By Label はラベル単位で分割される。同じグループでも Scene は別になる。また、ラベルの付いていないアセットはまとめて1つの AssetBundle になる。

f:id:yotiky:20210205163631p:plain:w300

Window > Asset Management > Addressables > Analyze を開くと分割の内容を解析できる。(前述の画像)

light11.hatenadiary.com

キャッシュ

設定

Project ウィンドウで Addressables > Initialization > Cache Initialization Settings を追加する。

f:id:yotiky:20210207170304p:plain

f:id:yotiky:20210207170435p:plain:w300

設定した CacheInitializationSettings を AddressableAssetSettings の Initialization Objects に追加する。

f:id:yotiky:20210207172035p:plain:w300

プロパティ 内容
Compress Bundles キャッシュに保存される AssetBundle を LZ4 形式で圧縮するかどうか
Cache Directory Override キャッシュの保存先
Expiration Delay キャッシュの保存期間(最大12960000秒(150日))
Limit Cache Size 最大キャッシュサイズを有効にするかどうか

保存先

Unity Editor (Windows)

リモートのコンテンツカタログがキャッシュされる場所。

C:\Users\<UserName>\AppData\LocalLow\<CompanyName>\<ProductName>\com.unity.addressables

f:id:yotiky:20210205205330p:plain:w200

ダウンロードした AssetBundle がキャッシュされる場所。

C:\Users\<UserName>\AppData\LocalLow\Unity\<CompanyName>_<ProductName>

f:id:yotiky:20210205205555p:plain:w250

グループの設定に以下の項目がある。(機能は未調査)

f:id:yotiky:20210205235518p:plain:w300

参考:qiita.com

HoloLens2

リモートのコンテンツカタログがキャッシュされる場所。

C:\Data\Users\<UserName>\AppData\Local\Packages\<ApplicationName_hash>\LocalState\com.unity.addressables

ダウンロードした AssetBundle がキャッシュされる場所。

C:\Data\Users\<UserName>\AppData\Local\Packages\MR-<ApplicationName_hash>LocalState\UnityCache\Shared

削除

ダウンロードした AssetBundle のキャッシュを削除する。コンテンツカタログは削除されない。

Caching.ClearCache();

補足

Addressables Event Viewer で参照カウントを可視化する

Addressables Event Viewer (旧名 RM Profiler)を使うと参照カウントを可視化したり、アセットが含まれている AssetBundle を確認することができる。

機能を有効にするには、Window > Asset Management > Addressables > Settings を開いて、[Send Profiler Events] にチェックを付ける。

f:id:yotiky:20210205021610p:plain:w300 f:id:yotiky:20210205021643p:plain:w200

light11.hatenadiary.com

Addressables で NullReferenceException

NullReferenceException when using Addressables.DownloadDependenciesAsync(label) - Unity Forum

Addressables のメソッドを呼び出して Taskawait しようとした時に Task 自体が nullNullReferenceException が発生する場合があるらしい。

以下のようにラップしてしまう拡張メソッドを用意するか、await して取り出すような拡張メソッドを用意する。

public static class AsyncOperationHandleExtensions
{
    public static Task<T> NotNullTask<T>(this AsyncOperationHandle<T> handle)
        => handle.Task ?? Task.FromResult(handle.Result);
}

別プロジェクトで作成したコンテンツカタログを読み込む

// ファイルパスかURL
var catalog = await Addressables.LoadContentCatalogAsync("http://169.254.37.215:12345/catalog.json");

// カタログは追加しなくても動きそう
//Addressables.AddResourceLocator(catalog);

await Addressables.InstantiateAsync("Assets/Prefabs/Sphere.prefab");

tsubakit1.hateblo.jp

ダウンロード処理のカスタマイズ

light11.hatenadiary.com

便利ツール

スクリプトからの操作

Addressables クラス

// 手動での初期化
// 呼び出していない場合は、LoadAssetAsync などが呼ばれた時に実行される
// https://docs.unity3d.com/Packages/com.unity.addressables@1.13/manual/InitializeAsync.html
await Addressables.InitializeAsync();

// 追加するコンテンツカタログをロードする
// パスはファイルの絶対パスやURLなど
// https://baba-s.hatenablog.com/entry/2020/03/19/063000
await Addressables.LoadContentCatalogAsync("CatalogPath");

// AssetBundle のサイズを取得する
await Addressables.GetDownloadSizeAsync("key");

// 事前に AssetBundle をダウンロードする
// https://baba-s.hatenablog.com/entry/2020/03/19/042000_1
await Addressables.DownloadDependenciesAsync("key");

参考

MRTK v2 - チートシート

目次

検証環境

  • MRTK 2.5.3

機能

Profiles

Spatial Awareness

  • Clone する Profile ファミリー
    • DefaultHoloLens2ConfigurationProfile
    • DefaultMixedRealitySpatialAwarenessSystemProfile
  • 設定
    • DefaultHololens2ConfigurationProfile をクローン
    • MixedRealityToolkit スクリプト > Spatial Awareness セクション で [Enable Spatial Awareness] を ON
    • DefaultMixedRealitySpatialAwarenessSystemProfile をクローン

yotiky.hatenablog.com

ハンドメッシュを表示する

  • Clone する Profile ファミリー
    • DefaultHoloLens2ConfigurationProfile
    • DefaultHoloLens2InputSystemProfile
    • DefaultHoloLens2HandTrackingProfile
  • 設定

    • DefaultHololens2ConfigurationProfile をクローン
    • DefaultHoloLens2InputSystemProfile をクローン
    • DefaultHoloLens2HandTrackingProfile をクローン
    • [Hand Mesh Visualization Modes] で [Player] を選択する

    f:id:yotiky:20210131162954p:plain:w280 f:id:yotiky:20210131163013p:plain:w100

  • Experimental で Pulse メッシュが含まれている

    f:id:yotiky:20210131163556p:plain:w200 f:id:yotiky:20210131163005p:plain:w200

    Pulse shader | Mixed Reality Toolkit Documentation

Mixed Reality Capture (MRC) でハンドメッシュがズレる

  • MixedRealityToolkitConfigurationProfile の Camera の項目で、DefaultHoloLens2CameraProfile をクローン
  • CameraSettingsProviders の DefaultWindowsMixedRealityCameraSettingsProfile をクローンして [Render from PV Camera] をONにする
  • Render from the PV camera (opt-in)

RiggedHandVisualizer (Experimental)

  • f:id:yotiky:20210125044256p:plain:w200
  • Clone する Profile ファミリー
    • DefaultHoloLens2ConfigurationProfile
    • DefaultHoloLens2InputSystemProfile
    • DefaultMixedRealityControllerVisualizationProfile
  • 設定
    • DefaultHololens2ConfigurationProfile をクローン
    • DefaultHoloLens2InputSystemProfile をクローン
    • DefaultMixedRealityControllerVisualizationProfile をクローン
    • Global Left Hand Visualizer および Global Right Hand VisualizerRiggedHandLeft(Right) を設定 f:id:yotiky:20210125044113p:plain

Object Manipulator

Bounds Control

Constraint Manager

Transform の動きに制約を適用できる

Interactable (TODO)

Button

  • MRTK Toolbox もしくは Packages/com.microsoft.mixedreality.toolkit.foundation/SDK/Features/UX/Interactable/Prefabs から追加 f:id:yotiky:20210125032414p:plain

  • 基本的な設定は Button Config Helper コンポーネント

    • OnClick イベントに処理を登録する
    • 見た目を変更する
  • アイコンを追加するには、Icon Set を作成する

yotiky.hatenablog.com

yotiky.hatenablog.com

Keyboard

  • 実機でしか表示されない
public TextMeshPro tmp;
private TouchScreenKeyboard keyboard;

public void OpenKeyboard()
{
    keyboard = TouchScreenKeyboard.Open("", TouchScreenKeyboardType.Default, false, false, false, false);
}

void Update()
{
    if (keyboard != null)
    {
        tmp.text = keyboard.text;
    }
}

Slate

Solver (TODO)

Object Collection (TODO)

Scroll Object Collection (TODO)

Tooltip (TODO)

Slider (TODO)

Progress Indicator (TODO)

Hand Menu (TODO)

Near Menu

  • MRTK Toolbox もしくは Packages/com.microsoft.mixedreality.toolkit.foundation/SDK/Features/UX/Prefabs/Menus から追加 f:id:yotiky:20210125031316p:plain

yotiky.hatenablog.com

App Bar (TODO)

Dialog (Experimental) (TODO)

Hand Coach (Experimental) (TODO)

Pulse Shader (Experimental)

Spatial Awareness

  • Clone する Profile ファミリー
    • DefaultHoloLens2ConfigurationProfile
    • DefaultMixedRealitySpatialAwarenessSystemProfile
    • DefaultMixedRealitySpatialAwarenessMeshObserverProfile
  • 設定
    • Material を新規作成し、SR_Triangles シェーダーに設定し MRTK_Pulse_SpatialMeshBlue もしくは Purple からプロパティをコピーする
    • [Auto Pulse] のチェックをONにする
      • ライブラリに含まれているマテリアルは、OFFになっているのでスクリプトなどから有効にする必要がある
    • DefaultHololens2ConfigurationProfile をクローン
    • MixedRealityToolkit スクリプト > Spatial Awareness セクション で [Enable Spatial Awareness] を ON
    • DefaultMixedRealitySpatialAwarenessSystemProfile をクローン
    • DefaultMixedRealitySpatialAwarenessMeshObserverProfile をクローン
    • Display Settings の Visible Material に 作成したマテリアルを設定する

f:id:yotiky:20210131164752p:plain:w300 f:id:yotiky:20210131164805p:plain:w200

ハンドメッシュ

  • 必要な手順は「ハンドメッシュを表示する」の項目を参照
  • Hand Mesh Prefab に Packages/com.microsoft.mixedreality.toolkit.foundation/SDK/StandardAssets/Prefabs/ArticulatedHandMeshPulse.prefab を設定する

f:id:yotiky:20210131162954p:plain:w300 f:id:yotiky:20210131163556p:plain:w200

Dock (Experimental) (TODO)

ElasticSystem (TODO)

Joy Stick

  • Packages/com.microsoft.mixedreality.toolkit.foundation/SDK/Experimental/Joystick/JoystickPrefab.prefab から追加 f:id:yotiky:20210125035321p:plain
  • Target Object に対象を登録する

Azure Managed ID を利用する

目次

概要

Managed ID は、 Azure Active Directory (Azure AD) で提供されるリソースに割り当てられた ID 。 Managed ID を有効にすることで Azure AD による管理と資格情報を使わないサービス間の認証を可能にする。

以前は Azure Managed Service ID の名称だった。

Managed ID の種類は2種類。

  • システム割り当て

    リソースに対して Azure AD によって作成される。ライフサイクルはリソースとひも付き、リソースに対して1対1となる。

  • ユーザー割り当て

    各リソースとは別にスタンドアローンとして作成される。ライフサイクルは独自になり、複数のリソースに割り当てて共有することができる。

docs.microsoft.com

f:id:yotiky:20210131142751p:plain(引用)

基本的な流れ。

  • Managed ID をサポートするサービス(Source)に対して ID を有効にする。
  • Azure AD 認証をサポートしているサービス(Target)に対して、上記 ID にアクセスを割り当てる。
  • プログラムからは接続情報などは用いず、各 Target のエンドポイントを使ってアクセスする。

docs.microsoft.com

Azure Functions にアクセスを割り当てる場合

対象の Azure Functions を開いて設定から ID を選択する。

f:id:yotiky:20210130214151p:plain:w300

システム割り当て済みをオンにする。

f:id:yotiky:20210130214226p:plain:w300

例えば App Configuration や Azure Storage を開いてアクセス制御(IAM)を選択し、Functions にロールの割り当てを追加する。

f:id:yotiky:20210131031856p:plain

Azure Key Vault ではアクセス制御(IAM)ではなく、アクセスポリシーを使って許可を与える。シークレット、キー、証明書に対して、各リソースがどのような操作を実行できるかを細かく設定できる。

f:id:yotiky:20210131150500p:plain

Azure Key Vault アクセス ポリシーを割り当てる (ポータル) | Microsoft Docs

プログラムからは、App Configuration の設定のアクセスキーからエンドポイントを取得してこれを使って接続する。

class Startup : FunctionsStartup
{
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
        
        builder.ConfigurationBuilder.AddAzureAppConfiguration(options =>
                options.Connect(new Uri(builtConfig["AppConfigEndpoint"]), new ManagedIdentityCredential()));
    }
}

マネージド ID を使用して App Configuration にアクセスする - Azure App Configuration | Microsoft Docs

Blob ストレージの場合。

    var containerEndpoint = string.Format("https://{0}.blob.core.windows.net/{1}",
                                                accountName,
                                                containerName);
    var containerClient = new BlobContainerClient(new Uri(containerEndpoint),
                                                                    new DefaultAzureCredential());

マネージド ID を使用してデータへのアクセスを認証する - Azure Storage | Microsoft Docs

Key Vault もエンドポイントを使って接続する。

class Startup : FunctionsStartup
{
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
            var builtConfig = builder.ConfigurationBuilder.Build();

            builder.ConfigurationBuilder.AddAzureKeyVault(new Uri(builtConfig["KeyVaultEndpoint"]), new DefaultAzureCredential());
    }
}

関連記事

参考

Azure Functions で Azure App Configuration から設定を読み込む

Azure App Configuration は複数のアプリケーションで、アプリケーション設定と機能フラグを一元に管理し共有するサービス。

目次

App Configuration を設定する

Azure App Configuration を作成する。

操作の構成エクスプローラーから「鍵」と「値」を追加する。

f:id:yotiky:20210131021953p:plain

App Configuration ストアに接続する

接続文字列

実装

作成した App Configuration の設定からアクセスキーを選択し、読み取り専用キーから接続文字列をコピーする。

f:id:yotiky:20210131021159p:plain

Functions のアプリケーション設定に接続文字列を登録する。

f:id:yotiky:20210131024550p:plain

Nuget で Azure.Extensions.AspNetCore.Configuration.Secrets をインストールする。

Statup クラスで、接続文字列を使って Azure App Configuration プロバイダーを追加する。

class Startup : FunctionsStartup
{
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
        builder.ConfigurationBuilder.AddAzureAppConfiguration(builtConfig["AppConfigConnectionString"]);
    }
}

Functions 側は インジェクションした IConfiguration から App Configuration に追加した鍵で取得できる。

    public class Function1
    {
        private readonly IConfiguration _configuration;
        public Function1(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        [FunctionName("Function1")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation(_configuration["AppConfig:ConnectionString"]);

            return new OkObjectResult("This HTTP triggered function executed successfully.");
        }
    }

エンドポイント

Azure Functions に Managed Id を設定

デプロイ済みの Azure Functions の設定から ID を選択する。

f:id:yotiky:20210130214151p:plain:w300

システム割り当て済みをオンにする。

f:id:yotiky:20210130214226p:plain:w300

App Configuration へのアクセスの許可

App Configuration のアクセス制御(IAM)を選択し、Functions にロールの割り当てを追加する。

f:id:yotiky:20210131031856p:plain

実装

App Configuration の設定で読み取り専用キーからエンドポイントをコピーする。

Functions のアプリケーション設定にエンドポイントを登録する。

f:id:yotiky:20210131024550p:plain

Statup クラスで、エンドポイントを使って Azure App Configuration プロバイダーを追加する。 Functions 側は接続文字列の項目と同じ。

class Startup : FunctionsStartup
{
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
        
        builder.ConfigurationBuilder.AddAzureAppConfiguration(options =>
                options.Connect(new Uri(builtConfig["AppConfigEndpoint"]), new ManagedIdentityCredential()));
    }
}

関連記事

yotiky.hatenablog.com