Photon Fusion for UnityとPlayFabを連携し、データを永続化する

この記事は[Unityアドベントカレンダー2022](https://qiita.com/advent-calendar/2022/unity)の10日目の記事です

# はじめに

Photon FusionはドイツのExit Games社が開発し、GMOグローバルサイン・ホールディングス株式会社が日本展開を行っているオンラインゲーム開発向けネットワークエンジンです。

過去の記事では基本的な使い方を中心に紹介してきました。しかし、Photon Fusionはデータを永続化するためのものではありません。そのため、キャラクターのデータを保存したり、ランキングデータを残したりといった使い方をしたい場合には、自分でサーバーを用意するか、外部のサービスを利用する必要があります。

そこで本記事では、MicrosoftのサービスであるPlayFabを用いて、Photon Fusionを使いながらデータを永続化する方法についてまとめました。

### 前提記事

Photon Fusionの基本的な解説は別記事で行っています。詳しく知りたい方は以下のリンクからご参照下さい。

[Photon Fusion for Unityの導入手順とPUN2との機能比較](https://qiita.com/nimushiki/items/d6fe4f0cbe0437677fa8)

関連記事

https://qiita.com/tags/photonfusion

### 動作確認環境

Windows 10 Home 21H2

Unity 2021.3.5f1

Fusion SDK 1.1.4 F Build 609

# PlayFabとは

Microsoftが提供するゲーム特化のBaaS（Backend as a Service）で、類似サービスの中でも特に利用例の多いものです。

有料サービスではありますが、ざっくり10万ユーザーまでは無料で使えるため、個人規模であれば無料の範囲内で使用可能でしょう。

https://learn.microsoft.com/ja-jp/gaming/playfab/what-is-playfab

# PlayFabの導入

PlayFabを始めるにあたって、Microsoft のGame Stack Ambassadorでもあるねこじょーかーさんのサイトが非常に参考になりました。

以下の記事で、0から始めてセットアップ、動作確認までの内容について解説しています。

https://playfab-master.com/playfab-how-to-start

以降の解説は、初回ログイン及び動作確認が終わっていることを前提に進めます。

## アカウントの作成

ユーザーがPlayFabの機能を使えるようにするためには、まずはPlayfab内にアカウントを作成して認証を行う必要があります。

一時登録（LoginWithCustomIDなど）と正式登録（AddUsernamePasswordなど）があります

一時登録はユーザーにめんどくさいと思われないため、最低限の情報でアカウントを作成する方法です。ただし、正確な認証を行ったり、永続的なアカウント管理をするには情報が少ないため、ユーザーのデータを継続して正常に利用してもらうためには、正式登録を行う必要があります。

これは公式がベストプラクティスとして紹介している流れでもあります。

## データを保存、取得

アカウントが作成できれば、任意のデータを保存、取得が出来るようになります。

保存にはUpdateUserDataメソッドを利用し、データはstring型のkey-value形式で保存されます。json方式で保存することもできます。

保存したデータは、GetUserDataメソッドを利用すれば取得することが可能です。

# Photon Fusionとの併用実装例

さて、ここまではPhoton Fusionとは特に関係のない説明でしたが、ここからはPhoton Fusionとあわせた実装例を、MMORPGを目指す拙作QuantumFrontierを元に紹介します。

本作は2022年9月にUnityroomで行われた1週間ゲームジャムの投稿作品で、ブラウザ上（WebGL）で稼働します。

投稿した当時、認証やデータ保存機能はありませんでしたが、今回追加実装を行いました。

[https://unityroom.com/games/quantumfrontier](https://unityroom.com/games/quantumfrontier)

## 認証を行う

プレイヤーがゲームを起動後、すぐに遊べるよう名前だけ入れればゲームスタートするようにします。裏ではLoginWithCustomIDを行い、PlayFabへ一時登録を行っておきます。

![タイトル画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/1ea1a0a6-61c1-b075-03f9-71eeaf58fa42.png)
タイトル画面

LoginWithCustomIDではすでに使われているIDかどうかを事前に確認できません。そのため、他人と同じIDになった場合、アカウントを共有することになってしまいます。

それを回避するには、デバイスIDを使うのが簡単でしょう。当然デバイスが変わると認証ができないため、早めに正式登録を行うようゲーム内でユーザーを誘導します。

```Sample.cs
void SignUp()
    {
        PlayFabClientAPI.LoginWithCustomID(
            new LoginWithCustomIDRequest { CustomId = SystemInfo.deviceUniqueIdentifier, CreateAccount = true }
        , result =>
        {
            Debug.Log("一時ログイン成功:CustomId ID " + result.PlayFabId);
            StartSharedSession();
        }
        , error => Debug.Log("一時ログイン失敗:CustomId"));
    }
```

正式登録にはAddUsernamePasswordを使います。ID、パスワード、メールアドレスがあれば登録が可能です。

また、Googleアカウントなど外部サービスを使うメソッドもあります。
![ゲーム内の正式登録フォーム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/c26a2923-26a6-40ef-4efb-869890bf9adb.png)
ゲーム内の正式登録フォーム

```Sample.cs
void AddUsernamePassword()
    {
        PlayFabClientAPI.AddUsernamePassword(
            new AddUsernamePasswordRequest()
                {
                    Email = _playerEmailAdress,
                    Password = _playerPassword,
                    Username = _playerName,
                }
            , result =>
            {
                Debug.Log("アカウント情報の追加成功:AddUsernamePassword");
            }, error =>
            {
                Debug.Log("アカウント情報の追加失敗:AddUsernamePassword");
                Debug.Log(error.GenerateErrorReport());
            });
    }
```

メソッド名にある通り、実際は正式登録というよりもアカウント認証の強化になります。一時登録状態でもデータの保存や取得はでき、アカウントのデータも保持され続けます。

なお、正式登録に関わらず、CustomIdでのログインはUnlinkCustomIDメソッドで明示的に解除しない限り有効のままになります。

## データを保存する

アカウントが作成できたら、次はデータの保存です。

UpdateUserDataを使用するだけで保存が可能ですが、データが変化するたびに保存すると負荷の問題が出る可能性があるため、定期的に行うなど保存タイミングは考える必要があります。

今回はログアウトや切断した時に保存するようにします。

DespawnedはPhoton Fusionのコールバックで、オブジェクトがデスポーンした時に呼ばれます。

保存可能なデータは文字列だけなため、exp～itemのint型変数を文字列変換をして保存します。

なお、keyが存在しない場合は新規に作成して保存します。

```Sample.cs
public override void Despawned(NetworkRunner runner, bool hasState)
{
    SetPlayFabData();
}

private void SetPlayFabData()
{
    PlayFabClientAPI.UpdateUserData(
		new UpdateUserDataRequest()
		    {
		        Data = new Dictionary<string, string>() {
		            ["exp"] = exp.ToString(),
		            ["level"] = level.ToString(),
		            ["digLevel"] = digLevel.ToString(),
		            ["moveLevel"] = moveLevel.ToString(),
		            ["itemLevel"] = itemLevel.ToString(),
		            ["item"] = item.ToString(),
		        }
		    }, result => {
		        Debug.Log("データ更新成功");
		    }, error => {
		        Debug.Log("データ更新失敗");
		        Debug.Log(error.GenerateErrorReport());
		    });
}
```

## データを取得する

GetUserDataメソッドを使い、保存されたデータを取得します。

実装例では、ゲームを始めた時（キャラクターがスポーンした時）に取得するよう、Awakeの中で呼んでいます。

```Sample.cs
private void Awake()
{
    GetPlayFabData();
}

private void GetPlayFabData()
{
    PlayFabClientAPI.GetUserData(new GetUserDataRequest(){}
				, result => {
        exp = int.Parse(result.Data["exp"].Value);
        level = int.Parse(result.Data["level"].Value);
        digLevel = int.Parse(result.Data["digLevel"].Value);
        moveLevel = int.Parse(result.Data["moveLevel"].Value);
        itemLevel = int.Parse(result.Data["itemLevel"].Value);
        item = int.Parse(result.Data["item"].Value);
        Debug.Log("データ取得成功");
        UpdateStatusUI();
    }, error => {
        Debug.Log("データ取得失敗");
        Debug.Log(error.GenerateErrorReport());
        UpdateStatusUI();
    });
}
```

# まとめ

Photon Fusionで作ったオンラインゲームに認証・データ保存機能を実装してみましたが、連携するにあたって障害はなにもありませんでした。Photon Fusionだけでは難しい機能ですが、外部サービスを使うことで非常に簡単に実装できることがおわかりかと思います。

なお、今回のサンプルコードは必要最低限の実装に留めています。紹介していない認証方法やデバイスの考慮などについては、GitHubに公式から実用的なユーティリティークラスが公開されていますので、そちらを参考にしてみて下さい。

https://github.com/PlayFab/PlayFab-Samples/blob/master/VideoTutorialSamples/PlayFabAuthentication/Assets/PlayFabUI/Demo/Scripts/PlayFabAuthService.cs