e.blog

主にUnity/UE周りのことについてまとめていきます

UnityでAWSのS3を扱うためのメモ

目次

概要

今回は訳合って、画像をサーバにアップロードしてそれを扱いたいなと思い、AWSのS3(Amazon Simple Storage Service)をUnity(特にモバイルプラットフォーム)から利用したくて色々ハマったのでそのメモです。

今回の目的はS3を利用して画像をアップロード、ダウンロードする方法についてのまとめです。

ドキュメントはこちら。

docs.aws.amazon.com

今回の実装にあたりこちらの記事を参考にさせていただきました。

qiita.com

実現すること

  • Unityを利用してAmazon S3に画像のアップロード、ダウンロードを行う

テスト環境

  • Unity 2020.1.14f1
  • Oculus Quest 2

フロー

  • AWS SDK for .NETをダウンロードしてインポートする
  • Identity Pool IDを取得する
  • S3ののバケットを作成する
  • IAMの設定

AWS SDK for .NETをダウンロード・インポートする

参考にした記事ではAWS Mobile SDK for Unityをインポート、と書かれていますが現在はどうやらこれはサポート外らしく、これの代わりにAWS SDK for .NETを利用する必要があります。

ダウンロードは以下の公式サイトに書かれているzip圧縮されたものをダウンロードしてきます。
(Download the following ZIP file: aws-sdk-netstandard2.0.zipと書かれている箇所。リンクはこちら

docs.aws.amazon.com

上記について公式のブログでも発表されています。

aws.amazon.com

必要なDLLをPluginsフォルダにコピー

上記zipファイルを解凍するとたくさんのDLLが出てきます。が、すべてを利用する必要はなく、自分が利用したいAWSのサービス用のDLLのみをコピーします。
ただし、AWSSDK.Core.dllだけはすべてのDLLが参照しているのでコピーが必要です。

今回はS3へ画像のアップロード・ダウンロードを行うのが目的なので以下のDLLをコピーしました。

  • AWSSDK.Core
  • AWSSDK.S3
  • AWSSDK.CognitoIdentity
  • AWSSDK.SecurityToken

依存しているDLLもコピー(依存関係の解決)

さて、実はこれだけで話は終わりません。というのもこのSDKは元々はNuGet*1)で提供されているもので、NuGet経由でインストールした場合はそれに紐づく依存関係も解決した上でインストールしてくれます。

が、今回は手動で上記DLLたちをコピーしているので手動でこの依存関係を解決しないとなりません。

このあたりについては以下の記事を参考にさせていただきました。(めちゃ助かりました)

qiita.com

依存方法の解決方法としてはMicrosoft自身が方法を記載してくれているのでそちらを参考にすることができます。

docs.microsoft.com

参考にした記事でざっくりした説明がされているので引用させていただくと、

ざっくり言うと、NuGetのパッケージ提供ページの「Dependencies」から依存関係を辿り、各パッケージのページにてパッケージをダウンロード、拡張子を.nupkgから.zipに変更して解凍するとdllが得られるのでこれを繰り返す、というものです。

ということで、これに従って依存しているDLLを探していきます。
まずはNuGetのパッケージ提供ページへ行き、「AWS SDK」と検索するとパッケージの一覧が表示されます。

まずはAWSSDK.Coreのページを見てみます。すると以下のように詳細が表示され、その中に依存が記載されている箇所があります。

赤線を引いたところがポイントです。
.NETStandard 2.0と書かれている部分が依存しているDLLの情報です。(今回のSDKが.NET Standard 2.0用のものなので)

親切にリンクが張られているのでそのまま該当ページに飛びます。

すると同様に詳細ページが開きます。

まずは右側にあるDownload packageからパッケージをダウンロードします。
そしてページ下部にはさらに依存を示す情報が載っているので、以後同様に依存しているパッケージをダウンロードします。

最終的には以下のパッケージが必要になります。

  • Microsoft.Bcl.AsyncInterfaces
  • System.Threading.Tasks.Extensions
  • System.Runtime.CompilerServices.Unsafe

ダウンロードが終わったら、記事に記載されている手順に従って拡張子を.nupkg.zipに変更した上でzipファイルを解凍します。

解凍するといくつかのファイルが展開されるので、lib/netstandard2.0配下にあるDLLを、AWS SDKと同様にコピーします。

必要なDLLをすべてコピーし終わるとエラーが消えていると思います。

ちなみに依存を解決する前には以下のようなエラーが出ていると思います。

Assembly 'Assets/Plugins/AWSSDK.Core.dll' will not be loaded due to errors:
Unable to resolve reference 'Microsoft.Bcl.AsyncInterfaces'. Is the assembly missing or incompatible with the current platform?

AWS CoreがまさにMicrosoft.Bcl.AsyncInterfacesに依存していることを示すエラーですね。

ここから先はS3側のセットアップになります。

Identity Pool IDを取得する

以下の手順に従ってIdentity Pool IDを取得します。

  • Cognito Consoleを開く
  • IDプールの管理ボタンをクリック
  • 新しいIDプールの作成ボタンをクリック
  • 任意のIDプール名を入力
  • 認証されていない ID に対してアクセスを有効にするチェックボックスをオンにする
  • 許可をクリック

上記を実行すると最後にプラットフォーム選択画面が表示されるので、そこでUnityを選びます。
すると以下のようなコードが表示されるのでコピーしておきます。(実装時に利用します)

CognitoAWSCredentials credentials = new CognitoAWSCredentials (
    "us-east-2:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", // Identity Pool ID
    RegionEndpoint.USEast2 // Region
);

バケットの作成

以下の手順に従ってバケットを作成します。

これでバケットが作成されます。

IAMの設定

  • IAM Management Console*2)を開く
  • 左のメニューからロールをクリック
  • ロール名の中からCognito_<Identity_Pool_Name>Unauth_Roleを選択
  • ポリシーをアタッチをクリック
  • AmazonS3FullAccessを付与(検索して出てきた左記のチェックボックスをオンにしてポリシーのアタッチをクリック)

最初に参考にした記事ではポリシーを作成、という手順だったのですが手順通りにしても項目が見つかりませんでした。
ただやりたいことはアクセス権の付与なので、上記の方法で動作が確認できました。

C#で実装する

以上でセットアップと準備が終わりました。あとはC#で実際のコードを書いていきます。
以下から、ひとつずつ実装を見ていきます。

Credentialのセットアップ

Identity Pool IDを取得するのところで出てきたコードを利用します。
CognitoAWSCredentialsオブジェクトを生成しますが、一度生成したら使い回せるのでAwakeなどのタイミングで生成するといいでしょう。

[SerializeField] private string _poolID = "POOL_ID";
[SerializeField] private string _bucketName = "Bucket Name";

private void Awake()
{
    _credentials = new CognitoAWSCredentials(_poolID, RegionEndpoint.USEast2);
}

ここで生成したCognitoAWSCredentialsをダウンロード・アップロード時に利用します。

画像のダウンロード

まずは画像のダウンロードから見ていきましょう。

for .NET版になってからasync/awaitが使えるようになったので簡単にコードを記述することができるようになりました。
フローは、

  1. Getリクエストオブジェクトを生成する
  2. AmazonS3Clientオブジェクトを生成する
  3. (2)のクライアントを使ってリクエストをawaitする
  4. 取得したデータをメモリに読み込む
  5. byteデータを画像化

というステップです。

コードはそこまで長くないので、上記フローを意識して見てもらえると分かるかと思います。

public async UniTask<Texture2D> DownloadImage(string fileName)
{
    GetObjectRequest request = new GetObjectRequest
    {
        BucketName = _bucketName,
        Key = fileName,
    };

    AmazonS3Client client = new AmazonS3Client(_credentials, Region);

    byte[] data = null;

    Debug.Log("Will download a texture from AWS S3.");

    GetObjectResponse result = await client.GetObjectAsync(request);

    Debug.Log("Received a response.");

    MemoryStream stream = new MemoryStream();
    result.ResponseStream.CopyTo(stream);
    data = stream.ToArray();

    Texture2D tex = new Texture2D(1, 1);
    tex.LoadImage(data);
    tex.Apply();

    return tex;
}

画像のアップロード

次に画像のアップロードです。
以下の例はメモリから読み出してアップロードするという手順になります。

ちなみに、for Unity版だとPostObjectRequestオブジェクトを利用していたのですが、これがrenameされたようです。(UploadPartRequestを使う)

フローは以下の通りです。

  1. Texture2Dをbyte配列に変換する
  2. MemoryStreamを作成し、変換したbyte配列を書き込む
  3. UploadPartリクエストオブジェクトを生成する
  4. AmazonS3Clientオブジェクトを生成する
  5. (4)のクライアントを使ってリクエストを投げる(必要があればawaitする)

というステップです。

こちらもコードはそんなに長くないのでそのまま見てもらえれば分かるかと思います。

public async UniTask<string> UploadImage(Texture2D image)
{
    string filename = "Images/" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ".png";

    AmazonS3Client client = new AmazonS3Client(_credentials, RegionEndpoint.GetBySystemName(RegionEndpoint.USEast2.SystemName));

    byte[] data = image.EncodeToPNG();

    MemoryStream stream = new MemoryStream(data.Length);
    stream.Write(data, 0, data.Length);
    stream.Seek(0, SeekOrigin.Begin);

    var request = new UploadPartRequest
    {
        BucketName = _bucketName,
        Key = filename,
        InputStream = stream,
    };

    UploadPartResponse result = await client.UploadPartAsync(request);

    Debug.Log(result);

    stream.Close();

    return filename;
}

ひとつだけ注意点があって、MemoryStreamに書き込んだあとはシーケンスを戻す必要があります。

MemoryStream stream = new MemoryStream(data.Length);
stream.Write(data, 0, data.Length);
stream.Seek(0, SeekOrigin.Begin);

この最後の行(stream.Seek(0, SeekOrigin.Begin))を実行しないと0バイトの画像がアップされてしまうので注意してください。

ファイルからアップロードする

今回はすでにメモリにあるものをアップロードするためにMemoryStreamを利用しました。が、ファイルからアップロードしたいという場合もあると思います。

その場合でも、FileStreamを作ってリクエストに設定してあげればOKです。(なので逆を返せばストリームであればなんでも渡せるってことですね)

コード断片だけ書いておきます。

FileStream stream = new FileStream("path/to/file", FileMode.Open, FileAccess.Read, FileShare.Read);

var request = new UploadPartRequest
{
    BucketName = _bucketName,
    Key = filename,
    InputStream = stream,
};

まとめ

調べてすぐに出てくるfor Unity版がすでにサポートされていないっていうのはハマりポイントでした。
それ以外にも、解説してくれている記事を参考にセットアップするも、微妙に内容が異なって「これでいいのか・・?」と不安になりつつセットアップしていくのが大変でした。

今回の記事が誰かの役に立てば幸いです。

*1:NuGetは.NET Framework用のパッケージマネージャ

*2:Identity and Access Management