API Management (APIM) は、既存のバックエンドのサービスに対して一貫性のある最新の API ゲートウェイを迅速に作成する手段です。
API Management が組織にもたらす利点は、外部のパートナーや社内の開発者に API を公開することによって、社内に眠っているデータやサービスの可能性を発掘できることです。 どの企業も、その業務をデジタル プラットフォームで拡大し、新しい販路と顧客を開拓すると共に、既存の顧客との絆を深めようと模索しています。 API Management は、開発者の取り組み、ビジネス インサイト、分析、セキュリティ、保護を通じて API プログラムの価値を高め、企業にコア コンピテンシーをもたらします。 Azure API Management を任意のバックエンドで実行し、それに基づいて本格的な API プログラムを起動できます。
Azure Functions で Azure Cosmos DB (Table) を操作する
目次
検証環境
- 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 table.CreateIfNotExistsAsync(IndexingMode.Consistent, throughput: 400, null, CancellationToken.None);
プロビジョニングされたスループットの場合、throughput
を指定せずに作成すると 800 で作成されてしまうので注意。最低コストの2倍発生する。
IndexingMode
と defaultTimeToLive
はこの指定で手動で作成した時のデフォルト値と同じになる。
テーブルに設定されたスループットは、ポータル上の下図から確認できる。
挿入
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 results = tableResult.Results;
ソートしたい場合。
var where = TableQuery.GenerateFilterCondition(nameof(CustomerEntity.PartitionKey), QueryComparisons.Equal, entity.PartitionKey); var query = new TableQuery<CustomerEntity>() .Where(where) .Order(nameof(CustomerEntity.Timestamp)); var tableResult = await cloudTalbe.ExecuteQuerySegmentedAsync(query, null);
cloudTalbe.CreateQuery<CustomerEntity>()
を使おうとすると NotSupportedException
が発生するので注意*1
削除
取得時の Entity が書き換わってないかチェックして削除する場合。
await cloudTalbe.ExecuteAsync(TableOperation.Delete(entity));
気にせず削除する場合は ETag にワイルドカードを使う。
var entity = new CustomerEntity("Tanaka", "Taro"); entity.ETag = "*"; await cloudTalbe.ExecuteAsync(TableOperation.Delete(entity));
補足
パフォーマンスや料金の基準となる RU だが、クエリでの使用量を確認するにはいくつかの方法がある。 ただ Table API で用意されているのは今の所 SDK を使う方法しかない。
CosmosDBのリソース開いて Insights(プレビュー) で統計情報は見れる。(Appliction Insights ではない)
参考
- .NET Standard SDK を使用した Azure Cosmos DB Table API | Microsoft Docs
- Azure Cosmos DB の Table API についてよく寄せられる質問 | Microsoft Docs
- Azure Cosmos DB Emulator を使用したローカルでのインストールと開発 | Microsoft Docs
- Azure Cosmos DB入門(7) - ryuichi111stdの技術日記
- Azure Cosmos DB入門(目次) - ryuichi111stdの技術日記
- Azure Cosmos DB の課金内容の理解 | Microsoft Docs
- Azure Cosmos DB の無償枠 (Free Tier) の注意点 - Qiita
- Azure Cosmos DB での要求のコストの最適化 | Microsoft Docs
関連記事
- Azure Functions で Azure Table Storage を操作する
- Azure Functions で Azure Queue Storage を操作する
- Azure Functions で Azure Blob Storage にファイルを保存する
- Azure Functions で Azure Blob Storage からファイルを取得する
- Azure Functions で Azure Storage への接続情報
- Azure Functions で Azure Key Valut から設定を読み込む
- Azure Functions で Startup クラスを定義して DI を利用する
- Azure Managed ID を利用する
Azure Functions で Azure Queue Storage を操作する
目次
検証環境
- Azure Functions v3
- Azure.Storage.Queues v12.6.0
実装
NuGet でライブラリをインストールする。
using Azure.Storage.Queues; using Azure.Storage.Queues.Models;
キューの作成
var connectionString = "ConnectionString"; var queueName = "QueueName”; var queueClient = new QueueClient(connectionString, queueName); await queueClient.CreateAsync();
メッセージの追加
await queueClient.SendMessageAsync("First"); await queueClient.SendMessageAsync("Second"); // 最近はBinaryも追加できるらしい var binary = new BinaryData(new byte[0]); await queueClient.SendMessageAsync(binary);
メッセージの表示
Azure.Response
でラップされてるので Value
プロパティを参照する。
var message = await queueClient.PeekMessageAsync(); log.LogInformation(message.Value.MessageId + ":" + message.Value.MessageText); var messages = await queueClient.PeekMessagesAsync(maxMessages:5); foreach (var msg in messages.Value) { log.LogInformation(msg.MessageId + ":" + msg.MessageText); }
メッセージの更新
キューは積み直しになる。
ReceiveMessageAsync
ではなく SendMessageAsync
の戻り値を使って更新しても同じ。
var receipt = await queueClient.ReceiveMessageAsync();
await queueClient.UpdateMessageAsync(receipt.Value.MessageId, receipt.Value.PopReceipt, "Updated");
メッセージの受信
受信するとキューからは削除される。
Azure.Response
でラップされてるので Value
プロパティを参照する。
var message = await queueClient.ReceiveMessageAsync();
log.LogInformation(message.Value.MessageId + ":" + message.Value.MessageText);
複数件受信する場合。
var messages = await queueClient.ReceiveMessagesAsync(maxMessages: 5); foreach (var msg in messages.Value) { log.LogInformation(msg.MessageId + ":" + msg.MessageText); }
メッセージの削除
ReceiveMessageAsync
で消えてしまうので、PeekedMessage
で PopReceipt
が取れないので使いみちあるのか謎。
SendMessageAsync
時に取り消すくらいには使えるかもしれない。
var message = await queueClient.ReceiveMessageAsync(); await queueClient.DeleteMessageAsync(message.Value.MessageId, message.Value.PopReceipt);
キューの削除
// キューが存在しない場合は例外発生 await queueClient.DeleteAsync(); // キューが存在しない場合に例外が出ない await queueClient.DeleteIfExistsAsync();
その他
メッセージのエンコード
Azure Functions のキュートリガーが文字列を受け取る場合、Base64 でエンコードされた値が想定されている。エンコードされていない値だと例外が発生する。 そのためキューに追加する時に、Base64 でエンコードした文字列を設定する必要がある。
SDK を使って文字列を追加する場合、SDK の v12 と v11 以前では動作が異なるので注意が必要。 v11 以前では自動的にエンコードされていたが、v12 ではエンコードされなくなったため明示的にエンコードする。
関連記事
- Azure Functions で Azure Cosmos DB (Table) を操作する
- Azure Functions で Azure Table Storage を操作する
- Azure Functions で Azure Blob Storage にファイルを保存する
- Azure Functions で Azure Blob Storage からファイルを取得する
- Azure Functions で Azure Storage への接続情報
- Azure Functions で Azure Key Valut から設定を読み込む
- Azure Functions で Startup クラスを定義して DI を利用する
- Azure Managed ID を利用する
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);
削除
取得時の Entity が書き換わってないかチェックして削除する場合。
await cloudTalbe.ExecuteAsync(TableOperation.Delete(entity));
気にせず削除する場合は ETag にワイルドカードを使う。
var entity = new CustomerEntity("Tanaka", "Taro"); entity.ETag = "*"; await cloudTalbe.ExecuteAsync(TableOperation.Delete(entity));
参考
- .NET Standard SDK を使用した Azure Cosmos DB Table API | Microsoft Docs
- Table storage でのデータの並べ替え
- ログ テール パターン
関連記事
- Azure Functions で Azure Cosmos DB (Table) を操作する
- Azure Functions で Azure Queue Storage を操作する
- Azure Functions で Azure Blob Storage にファイルを保存する
- Azure Functions で Azure Blob Storage からファイルを取得する
- Azure Functions で Azure Storage への接続情報
- Azure Functions で Azure Key Valut から設定を読み込む
- Azure Functions で Startup クラスを定義して DI を利用する
- Azure Managed ID を利用する
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 で Azure Cosmos DB (Table) を操作する
- Azure Functions で Azure Table Storage を操作する
- Azure Functions で Azure Queue Storage を操作する
- Azure Functions で Azure Blob Storage からファイルを取得する
- Azure Functions で Azure Storage への接続情報
- Azure Functions で Azure Key Valut から設定を読み込む
- Azure Functions で Startup クラスを定義して DI を利用する
- Azure Managed ID を利用する
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種類。
制御できる項目。
- アクセスできるリソース
- 書き込み、読み取りなどのアクセス許可
- トークンの有効期限
- IP制限
生成するには、ポータルや Microsoft Azure Storage Explorer を使う方法、プログラムで生成するなどがある。
ここではサービス SAS をプログラムで発行するサンプルとなる。 また、保存されているアクセス ポリシーにサービス SAS が関連付けられないため、強制的に失効させることができないので取り扱いは注意が必要。 有効期限を 1 時間以下に設定することが推奨されている。
実装
コンテナの 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
目次
- 目次
- 検証環境
- 概要
- インストール
- アセットを読み込む
- グループ
- グループとプロファイルの関係
- ラベル
- コンテンツカタログ
- Bundle Mode (Packing Mode)
- キャッシュ
- 補足
- 参考
検証環境
- 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] にチェックを付ける。
もしくは、Window > Asset Management > Addresables > Groups を開いて、対象のアセットをドラッグ&ドロップする。
スクリプトからは Addresable Name
を用いてアセットをロードするため、任意の名前をつける。
Play Mode Scripts で [Use Asset Database (faster)] を選択すると、実行時に AssetDatabase
を使って直接アセットを読み込むことができる。
続いてスクリプトから読み込む方法。
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
を渡す。
また、InstantiateAsync()
で生成された GameObject
のライフサイクルはシーンに紐づくので、シーンが破棄される時は同時自動で破棄される。
AssetReference
を使うと Inspector 上から Addressable に登録したアセットを設定できる。
ロード処理は非同期のため、戻り値である AsyncOperationHandle
の Task
プロパティを 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 の情報は表示されない。
[Simulate Groups] は、AssetDatabase
を使ってロードされるためアセットのビルドは不要だが、AssetBundle の依存関係を分析してシュミレーションすることができる。
[Use Existing Build] は、実際にビルドした AssetBundle をロードする。
AssetBundle をローカルから読み込む
Play Mode Script で [Use Existing Build] を選択する。
Addressables Groups ウィンドウでグループを選択すると、Inspector に設定が表示される。 Build Path と Load Path がどちらも Local になっていることを確認する。
Addressables Groups ウィンドウで New Build > Default Build Script を実行するとビルドが実行され AssetBundle が生成される。
スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。
AssetBundle をリモート(ローカルサーバー)から読み込む
Addressables Groups ウィンドウで Tools > Hosting Services を開いて、[Local Hosting] を作成する。
任意の [Service Name] を入力し、[Enable] にチェックを付けるとポートが割り当てられる。
続いて、Addressables Group ウィンドウで Profile > Manage Profiles で Addressables Profile ウィンドウを開いて、[Profile] を作成する。
任意の [Profile Name] を付けて、RemoteBuildPath に [LocalHostData/[BuildTarget]]、RemoteLoadPath に [http://[PrivateIpAddress]:[HostingServicePort]] と入力する。
Addressables Group ウィンドウで Profile から作成したプロファイルを選択する。
Addressables Group ウィンドウでグループを選択し、 Inspector 上で、 Build Path と Load Path に Remote を選択する。それぞれプロファイルで設定した内容が反映される。
同 Inspector から [Inspect Top Level Settings] をクリックして、AddressableAssetSettings を開く。 [Build Remote Catalog] にチェックを付け、Build Path と Load Path に Remote を選択する。
[Player Version Override] は指定しないとビルド日時で名前が生成されるため、ビルドするたびに新しいファイルが生成される。同じバージョン番号を指定しているうちはコンテンツカタログが上書き更新されるのでファイルが増え続けることはない。
Play Mode Script で [Use Existing Build] を選択する。
アセットをビルドすると、プロジェクト直下に LocalHostData
フォルダが作成され Catalog と AssetBundle が出力される。
スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。
AssetBundle をリモート(Azure Blob Storage)から読み込む
今回は、Azure Blob Storage にパブリックアクセスレベルを「BLOB」にした assets
という名前でコンテナを用意する。(危険なので使い終わったらさっさと除去しすること)
実際は SAS などを生成してセキリティに気をつける必要がある。
コンテナのプロパティから URL をメモしておく。
Addressables Groups ウィンドウで Create > Group > Packed Assets を選択して、グループを作成する。
任意のアドレス(Addressable Name)を付け、対象のアセットを登録する。
Group を選択して Inspector を開いて、Build Path と Load Path に Remote を選択する。
同 Inspector から Inspect Top Level Settings をクリックして、AddressableAssetSettings を開く。 [Build Remote Catalog] にチェックを付け、Build Path と Load Path に Remote を選択する。
Addressables Group ウィンドウで Profile > Manage Profiles で Addressables Profile ウィンドウを開いて、[Profile] を作成する。
任意の [Profile Name] を付けて、RemoteLoadPath に用意した Blob の URL を使ってhttps://xxx.blob.core.windows.net/assets/[BuildTarget]
と設定する。[BuildTarget] はビルドしたときのプラットフォームの文字列が入る。
Addressables Group ウィンドウで Profile から作成したプロファイルを選択する。
Play Mode Script で [Use Existing Build] を選択する。
アセットをビルドすると、プロジェクト直下に ServerData フォルダが作成され Catalog と AssetBundle が出力される。
作成した Catalog と AssetBundle を、Azure Blob Storage にアップロードする。 コンテナのパスなど RemoteLoadPath が変わった場合は再度アセットをビルドしてアップロードし直す。
スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。
グループ
フォルダをドラッグ&ドロップすることもできる。階層を維持する場合、フォルダに対してアドレス(Addresable Name)とラベルを設定できる。フォルダを指定してまとめて読み込むことはできなそう。 ラベルは配下のアセットにも適用されるので問題なくまとめて読み込むことができる。 アドレス(Addresable Name)もラベルも配下のアセットに個別に設定することはできないが、決められたアドレス(Addresable Name)を使えば個別に読み込むことは可能である。
フォルダが不要な場合はグループの直下に移動する。
グループとプロファイルの関係
Addressables Groups
グループでは、アセットにアドレス(Addresable Name)とラベルを付与して、グループに登録する。 アセットはいずれかのグループに所属し、複数のグループには登録できない。
グループの設定
グループ単位で AssetBundle 化されるため、グループの設定では、Build Path と Load Path が選択できたり、圧縮やプロバイダーの選択など、Packing や Loading などの設定ができる。
AddressableAssetSettings
Assets/AddressableAssetsData/AddressableAssetSettings
では、コンテンツカタログの設定ができる。コンテンツカタログを出力するかどうか、Build Path と Load Path をどこにするか、バージョンを上書きするかなどの設定が含まれる。
Addressables Profiles
プロファイルでは、ビルドとロードのパスをどこにするかの設定を作り分けられるようになっている。Unity Editor で実行する時、ローカルホストや開発サーバー、本番サーバーで実行する時などプロファイルを切り替えるだけでビルド先とロード先を変更できるようになる。
ビルド
各グループはグループ毎に Build Path / Load Path の設定を持つ。
コンテンツカタログも別途 Build Path / Load Path の設定を持ち、出力するように設定にする必要がある。
これらの Path は、選択した Profile によって値が切り替えられる。
コンテンツカタログは AddressableAssetSettings で Build Retemo Catalog にチェックを付けると生成されるようになる。
ビルドしたけど出力されていないと思った時は各グループ、コンテンツカタログの Build Path がどこになっているかを確認すると良い。
ラベル
アセットにラベルを付けてグルーピングすることができる。アセットでラベルを指定してまとめて読み込んだり、ダウンロードすることができる。
Addressables Groups ウィンドウで、Tools > Labels を開いて、ラベルを作成する。
Addressables Groups ウィンドウで任意のアセットにラベルを設定する。
Addressables.LoadAssetsAsync()
の引数にラベルを渡すとまとめてロードできる。(Assets
が複数形なので注意)
var objects = await Addressables.LoadAssetsAsync<Object>("Model", null); foreach(var item in objects) { Debug.Log(item); }
コンテンツカタログ
ビルド結果のファイル構成は以下の通り。
- catalog_<version>.hash
- catalog_<version>.json
- グループ毎の AssetBundle や依存関係のある AssetBundle など
hash ファイルは、コンテンツカタログの更新の有無を比較するために使われる。 hash ファイルに違いがある場合、リモートから json ファイルをロードする。 hash ファイルが同じ場合はローカルのキャッシュからロードされる。
これら hash ファイルと json ファイルの取得元やファイル名などの情報は、アプリのビルド時に settings.json を書き出され StreamingAssets に保存される。*2 アプリのビルドをしないと取得元を変更することができないため、[Player Version Override] はアプリのバージョンと同じにして、アプリが同じバージョンの間はコンテンツカタログの名前が変わらないようにして上書き更新し続けるのが良さそうである。
Bundle Mode (Packing Mode)
AssetBundle の分割単位を設定できる。 原則としてグループ毎に AssetBundle に分割され、グループ内であっても Scene とそれ以外では更に分割される。
- Pack Together
- Pack Separately
- Pack Together By Label
Pack Together はグループ単位でまとめられる。同じグループでも Scene は別になる。
Pack Separately はアドレス単位ですべて個別に分割される。
Pack Together By Label はラベル単位で分割される。同じグループでも Scene は別になる。また、ラベルの付いていないアセットはまとめて1つの AssetBundle になる。
Window > Asset Management > Addressables > Analyze を開くと分割の内容を解析できる。(前述の画像)
キャッシュ
設定
Project ウィンドウで Addressables > Initialization > Cache Initialization Settings を追加する。
設定した CacheInitializationSettings を AddressableAssetSettings の Initialization Objects に追加する。
プロパティ | 内容 |
---|---|
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
ダウンロードした AssetBundle がキャッシュされる場所。
C:\Users\<UserName>\AppData\LocalLow\Unity\<CompanyName>_<ProductName>
グループの設定に以下の項目がある。(機能は未調査)
参考: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] にチェックを付ける。
Addressables で NullReferenceException
NullReferenceException when using Addressables.DownloadDependenciesAsync(label) - Unity Forum
Addressables
のメソッドを呼び出して Task
を await
しようとした時に Task
自体が null
で NullReferenceException
が発生する場合があるらしい。
以下のようにラップしてしまう拡張メソッドを用意するか、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");
ダウンロード処理のカスタマイズ
便利ツール
- EZAddresser
- Unity Addressable Importer
スクリプトからの操作
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");
- 参考