Unity オンラインゲームにMicrosoft Azure Functions C# と PlayFab はいかが？

# まえがき
個人の趣味で大規模オンラインゲーム開発を進めるには、大手Webサービスを利用するほか道はないと思っています。
クラウドゲーミングサービス PlayFab を Microsoft が買収して2年、数日前に CloudScript というゲームクライアントからリクエストしてサーバーサイドで処理を実行する仕組みの実装本体に Azure Functions が一般利用できるようになりました。

ここにきて我々は C# だけでクライアント・サーバー処理を実装できるようになったわけです。
利用言語の統一は個人開発において開発効率の向上やモチベーションの継続に大きく寄与します。

本記事は他の多くの個人開発者の方にも参考にしていただければと思い、いつもは[個人ブログ](https://simplestar-tech.hatenablog.com/)に書いている内容をここ Qiita に残すことにしました。

# 前提

Azure のサービスを使うので Microsoft Azure にお支払いできる「従量課金」サブスクリプションが必要
Visual Studio 2019 を上記と紐づく Microsoft Account で利用している（ボタン押すだけで作業完了するので楽だよ）

# Visual Studio 経由で Azure Functions を発行

公式でとても十分な手順書があるのでなぞります
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio

ポイントだけ記録すると、新規プロジェクト作成で Azure Functions テンプレート C# を選択して適当な場所に適当なプロジェクト名で作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/96ba477a-705e-b6c8-5a64-f61a0729447f.png)

最新の v3 環境と Http trigger でリクエストをさばくようにし Authorization level を Function から Anonymous に落とします（一応 Function のままでもいけるけど、関数ごとに code クエリパラメータに関数のキー指定が必要になる。今は入門記事だからセキュリティは後から上げることにしました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/f0e0d49a-9940-3f87-c041-8b1991824f2f.png)

実装はテンプレート自動生成のままで進めます。

ソリューション エクスプローラーで、プロジェクトを右クリックし、 [発行] を選択
Azure Functions の従量課金プランの新規作成、パッケージファイルから実行（推奨）にチェックを入れてプロファイル作成に進みます

PlayFab の実行環境が US West （オレゴン州）なので Azure のリソースグループや Azure Storage の場所指定は West US (カリフォルニア州) or West US 2 （ワシントン州）で進めていきます。アメリカではそれぞれオレゴン州と隣接する州なので近い方がいいと思います。

数分後、Azure の Function App 一覧に West US ロケーションで Azure Function が作られていることを確認できました。（速度比較のため Japan East にも同じの作りました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/1aece772-3194-510d-5b15-b572e3a82746.png)

Function 一覧を確認して、まだ発行ボタンを押していないので一つも関数がない状態でした。
ここから Visual Studio 上で公開→発行ボタンを押すと Function1 が有効化された状態で確認できるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/77b44933-3c02-28e8-7718-a940bdf05a3d.png)

現在の実装はテンプレートから自動生成されたもの　以下の通りです。

```csharp
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace CubeWalkAzureFunctions
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }
}

```

# Azure Function の疎通確認

コンソールで関数について確認していけば「Get function URL」ボタンがあるので、ここから関数を HTTP リクエストで実行するための url を取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/5f122ae8-6cea-00be-938d-8b764ce37981.png)

上記実装では name クエリを必須としているので、これを付けて GET してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/de4b5c1b-2a2f-0a7b-f71c-0e540b607263.png)

Hello, simplestar が返ってきました。
これが確認できれば Azure Functions の作成、発行については問題ありませんね。

# Unity から PlayFab Azure Function を呼び出そう

## 前提
Unity で Cloud Script を実行できること

初心者で何もわからない方は先に以下の記事をなぞりましょう。

@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 へログインさせるための手順

## PlayFab にて関数名とurlを登録

自分の PlayFab スタジオ・タイトルの Automation → Cloud Script → Functions(Preview) で Register Cloud Script Function する
具体的には、関数の url を関数名とともに登録して紐づけを行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/2b8beff5-fe88-03a3-0417-8c99b9e0cfe3.png)

## Unity 側の実装

Unity で PlayFab にログインして API 呼べるなら、次の実装が行えるはずです。

```csharp
using PlayFab.CloudScriptModels;
using PlayFab;
using System.Collections.Generic;
using UnityEngine.Events;

~省略~

        private void CallCSharpExecuteFunction(UnityAction<string> onSuccess)
        {
            PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest()
            {
                FunctionName = "Function1", //This should be the name of your Azure Function that you created.
                FunctionParameter = new Dictionary<string, object>() { { "inputValue", "CubeWalk" } },
                GeneratePlayStreamEvent = false
            }, (ExecuteFunctionResult result) =>
            {
                if (true == result.FunctionResultTooLarge)
                {
                    Debug.Log("This can happen if you exceed the limit that can be returned from an Azure Function, See PlayFab Limits Page for details.");
                    return;
                }
                onSuccess?.Invoke(result.FunctionResult.ToString());
                Debug.Log($"The {result.FunctionName} function took {result.ExecutionTimeMilliseconds} to complete");
                Debug.Log($"Result: {result.FunctionResult.ToString()}");
            }, (PlayFabError error) =>
            {
                Debug.Log($"Opps Something went wrong: {error.GenerateErrorReport()}");
            });
        }
```

実行結果は次の通り

>
The Function1 function took 191 to complete
Result: Hello, simplestar

# PlayFab の SDK を Azure Function で利用

やっと下地が整いましたね。ここから PlayFab と Azure Function 連携のうまみをトッピングしていこうと思います。

# PlayFab SDK Package インストール

次のパッケージを Visual Studio プロジェクトにインストールします。
PlayFabAllSDK : https://www.nuget.org/packages/PlayFabAllSDK/
PlayFabCloudScriptPlugin : https://www.nuget.org/packages/PlayFabCloudScriptPlugin/

# PlayFab SDK の利用

テンプレート自動生成された Azure Function C# に次のクラスを追加します。
※PlayFab 側で TitleInternalData に TestKey で TestValue を詰めておいてください。以下のコードで取得できているかチェックしてます。
またタイトルIDとサーバー関数実行のためのシークレットキーをどこかのタイミングで渡しておきます。（今回は実行直前に設定…）
シークレットキーは PlayFab のタイトル管理画面の Secret Key 項目から作れます。
詳しい手順は[Azure PlayFab をサーバーサイド（ASP.NET Core Web API）から利用する](https://qiita.com/_y_minami/items/cf09b5552afb665f0aeb)から確認できます。

```csharp
using System.Net.Http;
using PlayFab.Plugins.CloudScript;
using System.Collections.Generic;
using PlayFab.ServerModels;
using PlayFab;

    public static class HelloWorld
    {
        [FunctionName("HelloWorld")]
        public static async Task<dynamic> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestMessage req,
            ILogger log)
        {
            /* Create the function execution's context through the request */
            var context = await FunctionContext<dynamic>.Create(req);
            var args = context.FunctionArgument;

            // コンテキストからプレイヤーIDを確認
            var message = $"CurrentPlayerId: {context.CurrentPlayerId}!";
            log.LogInformation(message);

            // POSTボディ入力パラメータを確認
            dynamic inputValue = null;
            if (args != null && args["inputValue"] != null)
            {
                inputValue = args["inputValue"];
            }
            message += $"  args: {new { input = inputValue } }";

            // サーバーAPIの呼び出し
            var internalData = await GetTitleInternalDataAsync();
            message += $"  internalData[TestKey] = {internalData["TestKey"]}";

            return new { messageValue = message };
        }

        [HttpGet]
        private static async Task<Dictionary<string, string>> GetTitleInternalDataAsync()
        {
            PlayFabSettings.staticSettings.TitleId = "ここに Playfab タイトルIDを指定";
            PlayFabSettings.staticSettings.DeveloperSecretKey = "ここに PlayFab タイトルのシークレットキーを指定";

            var request = new GetTitleDataRequest();
            var titleInternalData = await PlayFabServerAPI.GetTitleInternalDataAsync(request);

            return titleInternalData.Result.Data;
        }
    }
```

# 再発行→関数を登録→関数名で実行

加えたのは Azure Functions の実装だけですから、再発行→関数を登録→関数名で実行をしてみます。
問題なく PlayFab SDK が利用できていました。

結果（XXXXXXXX には有効な ID が入ります）
>
The HelloWorld function took 268 to complete
Result: {"messageValue":"CurrentPlayerId: XXXXXXXXXXXXXX!  args: { input = CubeWalk }  internalData[TestKey] = TestValue"}

# 速度の確認

West US と Japan East それぞれに同じ Azure Function を作ってみました。
それぞれの関数 url を利用する CloudScript を登録して、Unityにて関数実行からレスポンスが返るまでの時間を計測
結果が次の通り
※ 10 回ほど 2秒間隔で関数を Unity から実行

| ロケーション       |       Japan East |    West US    |
|:-----------------|------------------:|:------------------:|
| 所要最大[ms]     |              1061 |        492        |
| 所要最小[ms]     |            765 |       241     |

やっぱり Azure Functions も日本じゃなくてアメリカ西海岸に置くのがいいのかな
（全部日本に来ないかな…）

# まとめ

オンラインゲームにはいくつか形態があります。リアルタイム通信格闘だったり、ステータスをクエストで上げていくRPGだったり
今回のは後者のソーシャルRPGを支える PlayFab というクラウドゲーミングサービスのオンライン実装の話です

その Unity のオンライン機能実装が C# で、しかも Visual Studio 環境のインテリセンスを効かせてできるようになりました！
Azure Functions は F5 キーで実行するとサーバーとして機能しブラウザから HTTP リクエストすればブレークポイントはってデバッグもできます！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106364/142df9de-8f8e-415b-15d6-56d014dbc652.png)
こんなにうれしいことはない！！

昨今の Unity 個人開発で大規模オンライン要素の追加を考えている人は
Microsoft Azure Functions と PlayFab の組み合わせを候補にあげてみてはいかがでしょう？

# 参考情報

https://booth.pm/ja/items/2275245

以上です。