前置き
アセットのビルドを行ったとき、そのアセットで動作確認をしたいことがあります。その場合、ビルドしたら自動でアップロードしてくれるとありがたいですよね。
ということで、今回はAddressableAssetsSystem(以降AAS)のアセットをビルドして、その後そのままCloudflare R2(以降R2)へとアップロードするためのエディタ拡張を作成したいと思います。
R2はエグレス料金が無料となっておりますので、ユースケースにあうのであればなるべく使っていきたいと思い、検証のためにも採用しました。
なお、R2を使うにはサブスクリプション登録のためにクレジットカードなど支払い方法が必要なる点にご注意ください。ただし、サブスクリプション登録をしても実際に請求されるかどうかは使用量次第になります。
(今回の動作確認の範囲内であればほぼほぼ請求されないとは思いますが、Cloudflareのダッシュボードを確認しましょう。)
対象
- AASを使ったことがある方
- 基本的な紹介は省くので「導入記事とかは読んだ」以降のレベル感です
- R2に興味がある方
- 「S3や他のサービスは使っている」という方も参考にどうぞ
やること
- R2のセットアップ
- スクリプトからAASのビルドを実行する処理を追加
- スクリプトからR2へのアップロードを行う処理を追加
- ビルド処理とアップロード処理をまとめる
1. R2のセットアップ
アカウント作成処理は省略します
1. R2の支払いの設定
- すいません。スクショ撮影し忘れました。
- 必要な支払い情報を入力するだけだったかと思います。
2. バケットを作成
CloudflareのダッシュボードよりR2 オブジェクトストレージ
の概要
から作成できます
バケット名はなんでもよいので適当に入力してバケットを作成する
を選びましょう
3. R2 APIトークンを作成
R2 オブジェクトストレージ
の概要
からR2 API トークンの管理
から作成できます
トークン名
はなんでもよいです。今回は管理者用の設定にしています。TTLは好きなものでよいです。今回は記事用なので適当に1週間(で書けるだろうというてい)にしておきました。
作成後の画面にて表示されるアクセスキーID
、シークレットアクセスキー
、エンドポイント
は後ほど使用しますので覚えておいてください。また、このとき表示される情報は1回限りなのでご注意ください。(とはいえ、メモするのを忘れたら消して作り直せばOKです)
これでR2へのアップロードを行う準備が整いました。
2. スクリプトからAASのビルドを実行する処理を追加
ビルド処理自体は単純です。
AddressableAssetSettings.BuildPlayerContent(out var result);
上記のコードを実行することでAddressableAssetSettings
のBuild & Load Paths
に指定されているパスへと出力されます。(Remote
に指定している場合はServerData/[BuildTarget]
と、デフォルトで入力されているかと思います。)
上記の図の場合[ProjectHome]/ServerData/[BuildTarget]
以下にアセットが出力されるようになります。
(図の場合[BuildTarget]
の部分がStandaloneOSX
となっていますが、これは自動的に今のプラットフォームに合わせて表示が切り替えて表示されているからです)
なお、ContentStateData
を使用した更新の場合は下記の通りです。
ContentUpdateScript.BuildContentUpdate(AddressableAssetSettingsDefaultObject.Settings, "addressables_content_state.bin");
addressables_content_state.bin
には前回実行時に生成されているはずのファイルへのパスを指定してください。
ちなみに、実行するにはAddressables
のパッケージが必要ですので、Package Manager
よりAddressables
をインストールしておきましょう。(今更感ありますが)
3. スクリプトからR2へのアップロードを行う処理を追加
AWS SDK for .NET
を使用してアップロードを行います。
「ん?AWS?R2では?」となりますが、R2はAWSのS3互換のAPIを有しているためAWS SDKのS3用の処理を利用して操作を行うことが出来ます。
1. AWS SDK for .NET
のインストール
1-a. NuGetForUnity
のインストール
DLLの用意が面倒なので、NuGetForUnity
を使用します。
インストール手順は公式githubのインストール方法をご確認ください。
以下はInstall as GIT dependency via Package Manager
を使うパターンの例です。
1-b. AWSSDK.S3
のインストール
2.NuGet For Unity
のウィンドウよりAWSSDK.S3
をSearch
してInstall
をします
2. アップロードを行うEditor拡張用のクラスを作成
- UnityプロジェクトディレクトリのAssetsディレクトリ配下の任意の場所に
Editor
ディレクトリを作成します - 作成した
Editor
ディレクトリ以下に任意の名前のC#ファイルを作成します(今回は仮にAssetBuildAndUploader.cs
とします) - 以下のコードを追加します(このソースの説明は次で解説します)
アップロード処理のサンプル
using System;
using System.IO;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using UnityEditor;
using UnityEngine;
using Directory = System.IO.Directory;
using File = System.IO.File;
public class AssetBuildAndUploader
{
// R2への接続設定がかかれたJSONへのパス
private static string R2_CONFIG_JSON_PATH => Application.dataPath + "/../r2_config.json";
// ビルドされたアセットの出力先
private static string ServerAssetsPath => Application.dataPath + "/../ServerData/";
// R2への接続設定がかかれたJSONに対応するObject
[Serializable]
class R2Config
{
public string accessKey; // R2 APIのアクセスキー
public string secretAccessKey; // R2 APIのシークレット
public string r2Url; // R2へのエンドポイント
public string bucketName; // アップロード先のBucket名
}
[MenuItem("MyTools/AAS Upload To R2")]
public static async void UploadAssets()
{
Debug.Log("UploadAssets Start.");
// 出力先のディレクトリにあるアセットを全て取得してアップロードする
var files = Directory.GetFiles(ServerAssetsPath, "*.*", SearchOption.AllDirectories);
foreach (var file in files)
{
if (file.EndsWith(".DS_Store") || file.EndsWith("Thumbs.db"))
{
continue; // OS固有の不要なファイルは無視する
}
var fileInfo = new FileInfo(file);
// 保存先のキーは出力先の親ディレクトリの名前を全て除外したものにする
var directoryName = Path.GetDirectoryName(file).Replace(ServerAssetsPath, "");
Debug.Log($"Local: {file} ---> Remote: {directoryName}/{fileInfo.Name}");
// 実際のアップロード処理
await UploadToR2(file, $"{directoryName}/{fileInfo.Name}");
}
Debug.Log("UploadAssets Finished.");
}
private static async Task UploadToR2(string filePath, string keyName)
{
// アップロード先のR2の情報を読み込み
var r2Config = JsonUtility.FromJson<R2Config>
(
await File.ReadAllTextAsync(R2_CONFIG_JSON_PATH)
);
// R2のアップロード処理はS3と互換性があるのでS3Clientを利用する
var s3 = new AmazonS3Client
(
new BasicAWSCredentials(r2Config.accessKey, r2Config.secretAccessKey),
new AmazonS3Config
{
ServiceURL = r2Config.r2Url
}
);
try
{
// 指定されたファイルをアップロード
await s3.PutObjectAsync(new PutObjectRequest
{
BucketName = r2Config.bucketName,
Key = keyName,
InputStream = new FileStream(filePath, FileMode.Open),
DisablePayloadSigning = true
});
}
catch (AmazonS3Exception e)
{
Debug.Log($"e: {e.Message}");
}
}
}
r2_config.jsonのサンプル
{
"accessKey": "XXXXX(ここにR2 APIのアクセスキーID)",
"secretAccessKey": "YYYYY(ここにR2 APIのシークレットアクセスキー)",
"r2Url": "https://[account_id].r2.cloudflarestorage.com(ここにR2 APIのエンドポイント)",
"bucketName": "your-bucket-name(作成したバケットの名前)"
}
3. アップロード処理の説明
// R2への接続設定がかかれたJSONへのパス
private static string R2_CONFIG_JSON_PATH => Application.dataPath + "/../r2_config.json";
// 〜略〜
// R2への接続設定がかかれたJSONに対応するObject
[Serializable]
class R2Config
{
public string accessKey; // R2 APIのアクセスキー
public string secretAccessKey; // R2 APIのシークレット
public string r2Url; // R2へのエンドポイント
public string bucketName; // アップロード先のBucket名
}
// 〜略〜
var r2Config = JsonUtility.FromJson<R2Config>
(
await File.ReadAllTextAsync(R2_CONFIG_JSON_PATH)
);
上記はR2へのアップロード用の署名を保存しておくためのJSONファイルへのパスを定義し、それを読み込んでいます。このJSON仕様は今回の拡張機能用の独自仕様となります。ServiceURL
を書き換えるのが面倒だったのでこのようにしています。実運用の際にはファイルの取り扱いにはご注意ください。
// ビルドされたアセットの出力先
private static string ServerAssetsPath => Application.dataPath + "/../ServerData/";
上記はAASのビルド処理
にて記載した通り、プロジェクトフォルダ直下のServerData
以下に出力されているというていでの設定になります。ここはプロジェクトのAASのビルド後の出力先の設定を元に書き換えてください。
var s3 = new AmazonS3Client
(
new BasicAWSCredentials(r2Config.accessKey, r2Config.secretAccessKey),
new AmazonS3Config
{
ServiceURL = r2Config.r2Url
}
);
上記のBasicAWSCredentials
にはR2のセットアップ
にて取得したR2 APIトークンのアクセスキーIDとシークレットアクセスキーを指定しています。また、ServiceURL
にはR2のセットアップ
にて取得したエンドポイント
を指定しています。
await s3.PutObjectAsync(new PutObjectRequest
{
BucketName = r2Config.bucketName,
Key = keyName,
InputStream = new FileStream(filePath, FileMode.Open),
DisablePayloadSigning = true
});
上記では最終的に指定のファイルパスにあるデータを指定のbucketへとアップロードしています。
BucketName
はアップロード先のバケット名、Key
はバケット以下に配備されるときの名前、InputStream
はアップロードする対象のファイルのStreamです。
DisablePayloadSigning
はR2
では使えない署名を、AWS SDK
が使わないようにするため設定しています。これがtrue
となっていない場合STREAMING-AWS4-HMAC-SHA256-PAYLOAD not implemented
のエラーが発生しました。
4. ビルド処理とアップロード処理をまとめる
ビルド処理が成功した場合に、アップロード処理を行うようにすればよいでしょう。
単純に、以下のようになるでしょう。
AddressableAssetSettings.BuildPlayerContent(out var result);
if (!string.IsNullOrEmpty(result.Error))
{
Debug.LogError(result.Error);
}
else
{
AssetUploader.UploadAssets();
}
まとめ
以下は全体のソースコードになります
全体ソースコードのサンプル
using System;
using System.IO;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using UnityEditor;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using Directory = System.IO.Directory;
using File = System.IO.File;
public class AssetBuildAndUploader
{
// R2への接続設定がかかれたJSONへのパス
private static string R2_CONFIG_JSON_PATH => Application.dataPath + "/../r2_config.json";
// ビルドされたアセットの出力先
private static string ServerAssetsPath => Application.dataPath + "/../ServerData/";
// R2への接続設定がかかれたJSONに対応するObject
[Serializable]
class R2Config
{
public string accessKey; // R2 APIのアクセスキー
public string secretAccessKey; // R2 APIのシークレット
public string r2Url; // R2へのエンドポイント
public string bucketName; // アップロード先のBucket名
}
[MenuItem("MyTools/AAS Build And Upload")]
private static async void BuildAndUpload()
{
AddressableAssetSettings.BuildPlayerContent(out var result);
if (!string.IsNullOrEmpty(result.Error))
{
Debug.LogError(result.Error);
}
else
{
await UploadAssets();
}
}
private static async Task UploadAssets()
{
Debug.Log("UploadAssets Start.");
// 出力先のディレクトリにあるアセットを全て取得してアップロードする
var files = Directory.GetFiles(ServerAssetsPath, "*.*", SearchOption.AllDirectories);
foreach (var file in files) {
if (file.EndsWith(".DS_Store") || file.EndsWith("Thumbs.db"))
{
continue; // OS固有の不要なファイルは無視する
}
var fileInfo = new FileInfo(file);
// 保存先のキーは出力先の親ディレクトリの名前を全て除外したものにする
var directoryName = Path.GetDirectoryName(file).Replace(ServerAssetsPath, "");
Debug.Log($"Local: {file} ---> Remote: {directoryName}/{fileInfo.Name}");
// 実際のアップロード処理
await UploadToR2(file, $"{directoryName}/{fileInfo.Name}");
}
Debug.Log("UploadAssets Finished.");
}
private static async Task UploadToR2(string filePath, string keyName)
{
// アップロード先のR2の情報を読み込み
var r2Config = JsonUtility.FromJson<R2Config>
(
await File.ReadAllTextAsync(R2_CONFIG_JSON_PATH)
);
// R2のアップロード処理はS3と互換性があるのでS3Clientを利用する
var s3 = new AmazonS3Client
(
new BasicAWSCredentials(r2Config.accessKey, r2Config.secretAccessKey),
new AmazonS3Config
{
ServiceURL = r2Config.r2Url
}
);
try
{
// 指定されたファイルをアップロード
await s3.PutObjectAsync(new PutObjectRequest
{
BucketName = r2Config.bucketName,
Key = keyName,
InputStream = new FileStream(filePath, FileMode.Open),
DisablePayloadSigning = true
});
}
catch (AmazonS3Exception e)
{
Debug.Log($"e: {e.Message}");
}
}
}
ビルドしてアップロードを行うメソッドを新たに追加し、MenuItemの属性を追加しています。UnityのメニューよりMyTools
> AAS Build And Upload
を実行すると、ビルドしてアップロードが行われるようになります。
なお、今回で作成したR2のバケットは削除しておくと安心かと思います。
(ストレージ容量が超えていたり、外部アクセスをONにしてたりすると勝手に課金されてしまうかもしれないので)
以上となります。
バージョン情報
- Unity6000.0.25f1
- Addressables 2.2.2
- NuGetForUnity 4.1.1
- AWSSDK.S3 3.7.408.1
補足
R2へは初期設定のままだとURLでアクセスできません。
AASからアクセスするにはR2のバケットをパブリックアクセス
(要ドメイン)にするかCloudflare Workers
などからR2へと経由させる必要があります。
開発中の動作確認用などに関してはr2.dev サブドメイン
が利用できます。
こちらはバケットの設定
からr2.dev サブドメイン
の項目から設定可能です。
あとはAddressables Profiles
よりRemote
をCostum
にして、Remote.LoadPath
にパブリック r2.dev バケットURL
を指定すればOKです。