Unity+PlayFabで他ユーザのデータをランダムに取得する

# 使用バージョン
- Unity 2019.4.0f1
- PlayFab 2.87.200602

# 本記事の目的
Unity+PlayFabを使い、PlayFab上に登録してあるユーザの情報をランダムに獲得する

# はじめに
みなさんこんにちは。PlayFabの料金体系も変わり、無料でも100,000ユーザを超えないかぎりはかなり自由に使えるようになりました
（特に1,000MAUの制限が無くなった事は大変喜ばしく思います）
PlayFabはmBaasとして大変便利なもので、フレンド機能、ランキング機能、データストアなど数多くの機能がサーバ側のコードを書く必要がなく使えます。
そんなPlayFabですが、`他ユーザのデータをランダムに取得する`という機能がデフォルトで用意されていなかったので、
デフォルトでついているランキング機能を工夫して取得できるようにしてみました。
本記事ではPlayFabの導入などは書いておりませんので、必要な方は
https://qiita.com/naichilab/items/b286babac60932792f0a
などの記事をご参考ください。

また、この記事はこちらのフォーラムの投稿を参考にしています
https://community.playfab.com/questions/17943/how-to-get-random-playfabidgetting-random-player-i.html
こちらの通り、ランキングデータを使い別ユーザのデータを取得する。といった処理を書いています。

もしもっと良い方法などありましたら教えていただけると嬉しいです

#処理の流れ
この記事では以下の流れで他ユーザのデータを取得しています
全ユーザがランキングにランダムに値を登録
-全ユーザが`message`のデータを登録
-ランキングから自分の前後のプレイヤーを取得し、ランダムにどちらかを選択
-自分のランキングの位置を更新し、次のランダムで別データを取れるようにする
-取得したプレイヤーのIdからプレイヤーのデータを取得
-取得したプレイヤーデータのメッセージをログに表示

# ランキングの登録 ~PlayFab側~
PlayFabのダッシュボードから`ランキング`を選び、「新しいランキング」を登録します
<img width="1677" alt="ランキングクリック.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/d4c51d82-cd51-e950-9402-0555a07da4f3.png">

ランキングページで統計名情報を入れ、リセット頻度を「手動」に、集計方法は「最新」にして保存を押します

<img width="746" alt="情報入力.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/5dbdd190-3dcb-bbe2-d2be-3e16cdd68414.png">

これでサーバ側の準備は完了です。

# ランキングの登録 ~Unity側~
Unity側では以下のようなコードを書きます（PlayFabへのログインが必須ですが、省略します）

```
    // ランキング情報の登録
    public void UpdatePlayerStatistics() {
        var request = new UpdatePlayerStatisticsRequest{
            Statistics = new List<StatisticUpdate>{
                new StatisticUpdate{
                    StatisticName = "PinballData",   
                    //ランダムに値を入れる事でランダムな取得に対応する
                    Value = Random.Range(0, 1000000)
                }
            }
        };

        PlayFabClientAPI.UpdatePlayerStatistics(request, OnUpdatePlayerStatisticsSuccess, OnUpdatePlayerStatisticsFailure);
    }
    
    //スコア(統計情報)の更新成功
    private void OnUpdatePlayerStatisticsSuccess(UpdatePlayerStatisticsResult result){
        Debug.Log($"スコア(統計情報)の更新が成功しました");
    }

    //スコア(統計情報)の更新失敗
    private void OnUpdatePlayerStatisticsFailure(PlayFabError error){
        Debug.LogError($"スコア(統計情報)更新に失敗しました\n{error.GenerateErrorReport()}");
    }
```
ここでは`UpdatePlayerStatistics`を呼ぶ事で、自分のデータをランキングに保存しています。
`StatisticName`には先ほどPlayFabで登録したランキングの`統計情報名`を入れましょう。
`Value = Random.Range(0, 1000000)`
の部分は何をやっているかわかりにくいところですが、こちらは後ほどランキングからデータをランダムに取得するための準備となります。
`UpdatePlayerStatistics`を呼ぶ事でランキングに情報が入ります
<img width="1667" alt="ランキング情報.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/55ec41bc-9dae-933a-99ab-a9843341e5d6.png">

# 取得したいカスタムデータを登録する
ランキングの準備ができたら、そのデータを使う前に、欲しいデータを用意する必要があります。
今回は以下のように、`data`に`Hello!`を登録しています。

```
    public void SendUserMessage()
    {
        PlayFabClientAPI.UpdateUserData(new UpdateUserDataRequest() {
                Data = new Dictionary<string, string>() {
                    //stringとstringのペアを登録する
                    {"data", "Hello!"}
                },
                //他ユーザでも読めるようPermissionはPublicにしておく
                Permission = UserDataPermission.Public
            },
            result => Debug.Log("Successfully updated user data"),
            error => {
                Debug.Log("Got error setting user data Ancestor to Arthur");
                Debug.Log(error.GenerateErrorReport());
            });
        UpdatePlayerStatistics();
    }
```

（OnSuccessとOnErrorは今回は省略系の形で記述しています）
留意点としては、`Permission = UserDataPermission.Public`を指定しておかないと、他プレイヤーがデータを取得できません。
また、最後に`UpdatePlayerStatistics();`を呼ぶ事でランキングボードに自分のデータを登録しています
（初回だけでも大丈夫だと思います）
`SendUserMessage`を呼ぶ事でプレイヤーデータの`data`のKeyに`Hello!`が登録されているはずです
<img width="1664" alt="プレイヤーデータの確認.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/b2fe7cca-c109-304a-4660-2d4884170832.png">

# ランキングから対象のPlayFabIDを取得する
データの登録ができたら、今度はそのデータを取得してみましょう
ランキングの取得には` PlayFabClientAPI.GetLeaderboardAroundPlayer`
を使います。こちらはランキングの自分の周りのプレイヤーを取得できます。
具体的なコードは以下になります

```
    //ランキング情報を取得する
    public void GetRankingData()
    {
        var request = new GetLeaderboardAroundPlayerRequest
        {
            StatisticName = "PinballData",
            //自分と周りの+-1のデータを取得する
            MaxResultsCount = 3
        };

        PlayFabClientAPI.GetLeaderboardAroundPlayer(request, OnSuccess, OnError);
    }


    void OnSuccess(GetLeaderboardAroundPlayerResult leaderboardResult)
    {
        //取得したランキングからランダムにindexを取得
        int rnd = Random.Range(0, leaderboardResult.Leaderboard.Count);
        //取得したPlayFabIdを元にユーザデータを読み込む
        Debug.Log(leaderboardResult.Leaderboard[rnd].PlayFabId);
        //自分のランキングValueを更新しておく
        UpdatePlayerStatistics();
    }

    void OnError(PlayFabError error)
    {
        Debug.LogError($"スコア(統計情報)取得に失敗しました\n{error.GenerateErrorReport()}");
    }
```
`GetRankingData`の`MaxResultsCount`を3にする事で、自分とその周りのプレイヤーのデータを取得できます
この時、注意しなくてはいけないのは`自分のIdも含まれる`という事です。これを防ぐにはログイン時に自分のIdを保存しておき、
ランダム取得で自分のIdが抽選されてしまったら抽選し直し。とすると良いでしょう

`GetRankingData`を呼び、成功するとログにIdが書き出されます
<img width="799" alt="ログにIdを出す.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/e40aa719-66e6-eab5-47c1-640b21a5e45a.png">

# 取得したPlayFabIdを使いデータを問い合わせる
PlayFabIdの取得ができたら、そのIdを引数に`GetUserData`を使いデータを取得します
（GetAnotherUserData(leaderboardResult.Leaderboard[rnd].PlayFabId)のように指定します）

```
    public void GetAnotherUserData(string id)
    {
        var request = new GetUserDataRequest();
        request.PlayFabId = id;

        PlayFabClientAPI.GetUserData(request, OnSuccesGetUserData, OnErrorGetUserData);
    }

    void OnSuccesGetUserData(GetUserDataResult result)
    {
        Debug.Log(result.Data["data"].Value);
    }

    void OnErrorGetUserData(PlayFabError error)
    {
        Debug.LogError($"ユーザデータ取得に失敗しました\n{error.GenerateErrorReport()}");
    }
```

こちらを実行する事で、`data`に登録した`Hello`が表示されます
<img width="804" alt="Hello World.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/cd095aff-d86c-622e-5e9b-edd14e885c62.png">

# 終わりに
今回の実装は、ランキングボードを使いランダムに他プレイヤーのデータを取得する。という処理の実装でした。
正直もっと良い方法があるかとは思っていますが、自分のアプリでは一旦このように実装しています。
より良い方法やPlayFabのアップデートなどで簡単になった場合などはまた別の記事にしようかと思っているので、
その際はまたよろしくお願いいたします。