UnityでPlayFabのFileContentをDownloadしてSaveする

# まえがき
こんにち Unity でオンラインゲームを作る場合、バックエンドサービスと呼ばれる Microsoft や Google, Amazon が用意している API セットを利用するのが安く、早く、堅牢です。
今回は Microsoft のバックエンドサービスである PlayFab を使って、たとえ数百万のユーザーが一斉にダウンロードしはじめても、複数のサーバーによる負荷分散（コンテンツ配信ネットワークの利用）をする例を示して、PlayFab を利用する日本人 Unity 開発者を増やしていきたいと思います。

# 手順

## 1. 下準備（Unity から PlayFab SDK を使う準備と PlayFab へのログイン）
@ume67026265 さんが PlayFab のアカウントの作り方と、Unity へ SDK を導入する記事を書かれています。
不慣れな方はこちらを参考に進めるとスムーズに準備ができると思います。（私も常に参照させてもらっています）

  [PlayFab始めました](https://qiita.com/ume67026265/items/380e7068e9a2b60b26bc) ※アカウントの作り方
  [PlayFabでUnityを動かしてみる -その1下準備編 -](https://qiita.com/ume67026265/items/c89b391e36855f45586f) ※Unity へ PlayFab SDK を導入する手順
  [PlayFabでUnityを動かしてみる -その2 APIコールを作ってみる-](https://qiita.com/ume67026265/items/b9313c696ceaa301baa8) ※ユーザーを PlayFab へログインさせるための手順

API を呼ぶユーザーは決まって CustomId = "GettingStartedGuide" にリンクしたユーザーであると認識して進めます。
詳しく知りたい人は、次の個人ブログ記事を読んでください。ユーザー認証について触れています。
[PlayFab:UnityのバックエンドとしてのAWS入門](https://simplestar-tech.hatenablog.com/entry/2019/08/14/224657)

## 2. Unity 側のダウンロードコードの実装

下準備でカスタムIDユーザーとしてログインに成功していれば、次のコードで A キーを押せばダウンロードと保存を試みます。
（ファイルはアップロードしていないので、その旨のエラーを吐いて失敗します）

```cs:PlayFabDownloadFile.cs
using PlayFab;
using PlayFab.ClientModels;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;

public class PlayFabDownloadFile : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            var key = "cubes/000-000-000.bytes";
            this.DownloadFileFromCDN(key);
        }
    }

    public void DownloadFileFromCDN(string key)
    {
        GetDownloadUrl(key, presignedUrl =>
        {
            GetFile(key, presignedUrl);
        });
    }

    void GetDownloadUrl(string key, Action<string> onComplete)
    {
        PlayFabClientAPI.GetContentDownloadUrl(new GetContentDownloadUrlRequest()
        {
            Key = key,
            ThruCDN = true
        }, result => onComplete(result.URL),
        error => Debug.LogError(error.GenerateErrorReport()));
    }

    void GetFile(string key, string preauthorizedUrl)
    {
        StartCoroutine(this.GetData(key, preauthorizedUrl));
    }

    IEnumerator GetData(string key, string preauthorizedUrl)
    {
        UnityWebRequest www = UnityWebRequest.Get(preauthorizedUrl);
        yield return www.SendWebRequest();

        if (www.isNetworkError || www.isHttpError)
        {
            Debug.Log(www.error);
        }
        else
        {
            // 結果をテキストとして取得する例
            // Debug.Log(www.downloadHandler.text);

            // 結果をバイナリデータとして取得する
            byte[] results = www.downloadHandler.data;
            // バイナリファイルを保存する
            var fileName = Path.GetFileName(key);
            var filePath = Path.Combine(Application.persistentDataPath, fileName);
            File.WriteAllBytes(filePath, results);
        }
    }
}
```

## 3. ファイルをアップロードする

あなたのタイトルのゲームマネージャー（管理画面）を開いて
１．Content 項目を選択し
２．File Management タブを選択し
３．NEW FOLDER ボタンで cubes と名前を付けたフォルダを切り
４．UPLOAD FILES ボタンで、バイナリファイルを選択してアップロードします

![playFabContent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/beec7072-5382-8296-ca3d-f76dbe864480.png)

これで、先程の Unity コードを実行します。

成功しました。
アップロードしたファイルがダウンロード保存できてました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/c06a0e28-c898-79e7-b990-dabbd263dd5d.png)
そういえば ThruCDN = true とコードを書いていますが、false を指定すると最新のオリジナルを引っ張ってくるそうです。

# まとめ
PlayFab を使えば、簡単にコンテンツ配信＆ダウンロード保存ができることがわかりました。
API を使ってアップロードももちろんできます。
そういった詳細な情報を知りたくなった方は次の公式ドキュメントを当たってみてください
[コンテンツ配信ネットワークのクイックスタート](https://docs.microsoft.com/ja-jp/gaming/playfab/features/data/content-delivery-network/quickstart)
関連ドキュメントを読み進め始めたなら、あなたもついに Microsoft PlayFab ユーザーですね！（記事の目的達成です。）

PlayFab には他にも [PlayFab:Unityゲーム内課金システムPayPal編](https://simplestar-tech.hatenablog.com/entry/2019/08/16/234209) なんて魅力的な機能もあるんです。
これからの個人開発ゲームはフリーミアムといいますか…
>フリーミアム（Freemium）とは、基本的なサービスや製品は無料で提供し、さらに高度な機能や特別な機能については料金を課金する仕組みのビジネスモデルである

この知見があなたのエンジニア生活の足しになればと思います。
本記事が公式ドキュメントへの良い入り口にならんことを