PlayFab ExecuteCloudScriptで注意する点

# はじめに
PlayFabを使ったので初投稿です。
あまり深いことはしてないので今回unity1weekで使用した機能に焦点を当て、やったこと・苦労した点を書いていきます。

主にCloudScript周りの内容になります。

**※注意  APIの使用方法などは書いていません！**

# PlayFabについて
PlayFab ライブ ゲームを構築して運用するための完全な LiveOps バックエンド プラットフォーム
https://azure.microsoft.com/ja-jp/services/playfab/

MicroSoftさんが提供されているmBaaSになります。
有名なFPSのR6S(レインボーシックスシージ)でも使用されていて驚きました。

# CloudScript
使用するデータをPlayFabで編集&操作したりプラットフォーム側に渡したりすることができる機能になります。
無料で使用できる範囲が決まっていて制限はありますが、かなり猶予があるのでそこまで気にしなくても大丈夫だと思います。

詳しい制限についてはについては参考資料リンクの[PlayFab 各プランのAPI制限値とか比較してみた](#参考資料リンク)が参考になります。

unity1weekではほとんどのデータ操作をCloudScriptで済ませました。

# やったこと
- **Unityからプレイヤー固有データをPlayFabに受け渡す**
- **PlayFabのプレイヤー固有データをCloudScriptで操作してUnityに受け渡す**

上記の2点です。

### Unityからプレイヤー固有データをPlayFabに受け渡す
PlayFabにおいてはユーザーのことを**プレイヤー**と呼び、
プレイヤーごとにTitleDataというプレイヤー固有のデータを持つことができます。

ゲームをクリアするごとにプレイヤーの取得アイテムが保存されていく仕様だったのでプレイヤーのTitleDataに
Json形式で配列に上積みしていきました。

＊イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256718/a39fd916-d0c2-a8dc-79c4-51398e1927b9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256718/96d4efa3-7e49-5b66-918a-537364ec0c28.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256718/af11d343-8d4d-a1e1-b299-a141caa2ecce.png)
(PlayFabの画面内でJson編集できるのすっごく便利です)

```json:item.json
{
      "Item1": 14,
      "Item2": 10,
      "Item3": 1,
      "Item4": 17,
      "Item5": 8,
      "Item6": 19,
      "Item7": 5
}
```
データの持たせ方はもっと工夫したいです。
ゲームをクリアした時に上記のJson文字列をPlayFab側に渡してCloudScriptで
保存用ハンドラーオブジェクトを作成しアイテムを保存しました。


### PlayFabのプレイヤー固有データをCloudScriptで操作してUnityに受け渡す

```json:playerItem.json
{
  "result_list": [
    {
      "Item1": 14,
      "Item2": 10,
      "Item3": 1,
      "Item4": 17,
      "Item5": 8,
      "Item6": 19,
      "Item7": 5
    },
    {
      "Item1": 8,
      "Item2": 7,
      "Item3": 2,
      "Item4": 6,
      "Item5": 20,
      "Item6": 12,
      "Item7": 14
    }
  ]
}
```
PlayFabにはこのように保存されます。
このアイテムリストをCloudScriptで操作してUnityに渡します。
当初、データをUnityに渡すときに用意したハンドラーオブジェクトは3つでした。

1. **アイテムリストをリザルト用のテキストに変換してテキストをUnityに渡すハンドラーオブジェクト**
2. **あらかじめ用意してあるアイテムの属性を参照してアイテムリストから特定の数値を計算するハンドラーオブジェクト**
3. **プレイヤーのアイテムリストをUnityに渡すハンドラーオブジェクト**

PlayFabにアイテムリストを保存するハンドラーオブジェクトと上3つのハンドラーオブジェクト
合計4つのハンドラーオブジェクトをExecuteCloudScriptでUnity側から一気に呼び出しました。
(ゲームの仕様上、保存と受け取りを一緒に呼び出す必要がありました)

# ハマったこと
1. **PlayFabのAPI、ExecuteCloudScriptを使用した時のリクエスト結果待ちのタイミング**
2. **ExecuteCloudScriptの連続呼び出し、何故かWebGLがjavascriptエラーを吐いて止まる**

上記の問題にハマりunity1week提出に2週間遅刻しました(チームのメンバーにも迷惑を掛けた...)

### 1. PlayFabのAPI、**ExecuteCloudScript** を使用した時のリクエスト結果待ちのタイミング

**保存 → データ受け取り1 → データ受け取り2 → データ受け取り3**

この流れを間髪入れず行った結果、保存が終了する前に前回の結果を取得していました。
これはコールバック関数で保存が終了した後に受け取りの処理を流せばよいので
問題ないかと思います。
(タイミングはなんとかなるだろうと高を括った結果、見事失敗し恥をかきました)

### 2. ExecuteCloudScriptの連続呼び出し、何故かWebGLがクラッシュ
上記のバグを直した結果

```c#:ResultData.cs
ExecuteCloudScript()
{
   ExecuteCloudScriptのコールバック関数()
   {
     ExecuteCloudScript()
     {
        ExecuteCloudScriptのコールバック関数()
        {
           ExecuteCloudScript()
           {
              ....
           }
         }
     }
   }
}
```
このようにExecuteCloudScriptを4回連続で呼び出す苦しいコードになってしまいました。
が、データの保存、受け取り自体はこれで滞りなくできました！

**UnityのEditor上では・・・**

これを**WebGL**で動かそうとすると以下のjsエラーを吐いてゲームが止まってしまったのです。
どのようなエラーかわかる方いらっしゃれば教えてください・・・

<details><summary>エラー内容</summary>
<div>

```
UnityLoader.js:4 75
printErr @ UnityLoader.js:4
onAbort @ UnityLoader.js:4
abort @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous) @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
invoke_iii @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous) @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
invoke_iiiii @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous)
(anonymous) @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
browserIterationFunc @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
runIter @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Show 18 more frames
1349dacf-c8ae-49c0-8def-0573ada035a4:8 Uncaught abort(75) at Error
    at jsStackTrace (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:22313)
    at Object.stackTrace (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:22484)
    at Object.onAbort (http://localhost:56930/Build/UnityLoader.js:4:11047)
    at abort (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:510250)
    at wasm-function[70550]:0x131ef8c
    at wasm-function[22005]:0x964f6a
    at wasm-function[50357]:0xe88462
    at wasm-function[70419]:0x131e487
    at Object.dynCall_iii (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:479310)
    at invoke_iii (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:343197)
    at wasm-function[50354]:0xe8781e
    at wasm-function[50353]:0xe87631
    at wasm-function[50351]:0xe875bc
    at wasm-function[50350]:0xe87553
    at wasm-function[57883]:0x1002e79
    at wasm-function[31736]:0xb19e85
    at wasm-function[57248]:0xfd990c
    at wasm-function[57245]:0xfd845b
    at wasm-function[57242]:0xfd8297
    at wasm-function[57241]:0xfd8266
    at wasm-function[57238]:0xfd80a7
    at wasm-function[57240]:0xfd8203
    at wasm-function[30981]:0xaef401
    at wasm-function[30980]:0xaef06c
    at wasm-function[58615]:0x102c67d
    at wasm-function[26861]:0x9fbd76
    at wasm-function[66200]:0x126f8c7
    at wasm-function[25045]:0x9cf3d6
    at wasm-function[70426]:0x131e545
    at Object.dynCall_iiiii (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:481494)
    at invoke_iiiii (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:347245)
    at wasm-function[68474]:0x12c6bef
    at wasm-function[67784]:0x12a6bc3
    at wasm-function[4208]:0x1c3a48
    at wasm-function[4207]:0x1c3976
    at wasm-function[7708]:0x2f008c
    at wasm-function[7706]:0x2efd9c
    at wasm-function[7710]:0x2f0291
    at wasm-function[8096]:0x31db22
    at wasm-function[10447]:0x41cd66
    at wasm-function[10159]:0x3fa430
    at wasm-function[10159]:0x3fa445
    at wasm-function[10151]:0x3f9627
    at wasm-function[10145]:0x3f7a75
    at wasm-function[70447]:0x131e8dc
    at Object.dynCall_v (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:493057)
    at browserIterationFunc (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:181651)
    at Object.runIter (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:184712)
    at Browser_mainLoop_runner (blob:http://localhost:56930/1349dacf-c8ae-49c0-8def-0573ada035a4:8:183174)
runIter @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
requestAnimationFrame (async)
requestAnimationFrame @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_scheduler_rAF @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8
Browser_mainLoop_runner @ 1349dacf-c8ae-49c0-8def-0573ada035a4:8

```

</div>
</details>

# 原因究明

結局のところ原因の特定には至りませんでした。

少しだけ究明を試みたところ、ExecuteCloudScriptが成功、失敗いずれかを返したあとも
PlayFabのコードは何らかの処理をしていました。
この処理中に何度もリクエストを送るのが良くないのかもしれません。

**ExecuteCloudScript...というかPlayFabのAPIを連続で叩いてリクエストを送ると
CloudScriptでエラーが発生してWebGLがクラッシュしてしまうのでは？**

と仮説を立て、とりあえず1つだけ実行してみると成功！(これはわかっていた)

成功はしましたが今回はゲームの仕様上連続で呼ばなければいけないことに変わりはないので
「ロード画面を挟む」方法を考えました。
これにも「何秒待てば良いのかわからない」「無駄にゲームを止めたくない」など
問題があったため却下。

最終的に次の解決策に続きます。

# 解決策

### 1つのCloudScriptで必要なデータを一気にUnityに渡す

これで解決することにしました。

今までExecuteCloudScriptで呼び出していた

1. **アイテムリストをリザルト用のテキストに変換してテキストをUnityに渡すハンドラーオブジェクト**
2. **あらかじめ用意してあるアイテムの属性を参照してアイテムリストから特定の数値を計算するハンドラーオブジェクト**
3. **プレイヤーのアイテムリストをUnityに渡すハンドラーオブジェクト**

以上3つのハンドラーを普通の関数にして1つのハンドラーオブジェクトで全てを実行、
複数のKey,Valueを持ったJsonを戻り値にしてUnityに渡しました。

これによりExecuteCloudScriptの呼び出しは以下の2つに減らすことができ、先のエラーは消えました。

- **Unityからプレイヤー固有データをPlayFabに受け渡す**
- **PlayFabのプレイヤー固有データをCloudScriptで操作してUnityに受け渡す**

<details><summary>CloudScript全文</summary>
<div>

```js:CloudScript
// Unityに全リザルトデータを渡す
// ハンドラーオブジェクト
handlers.GetResultData = function()
{
    // 生存期間取得
    var survivedDays = CalcSurvivedDays();
    // リザルトテキスト取得
    var resultText = CreateText();
    // プレイヤーのアイテム履歴取得
    var resultHistory = GetPlayerResultsHistory();
    
    // 複数のKeyValueを持つJsonを返す
    return { Days : survivedDays, Text : resultText["Result"], History : resultHistory };
}

// プレイヤーのアイテム履歴取得
function GetPlayerResultsHistory()
{
    var food_1, food_2, drink, healing_goods, fuel, material;
    var resultTextList = [];
    var resultExsists = false;
    var playerTitleData = server.GetUserReadOnlyData({
       "PlayFabId":currentPlayerId,
       "Keys":["ResultItemList"]
    });
    if(playerTitleData.Data.hasOwnProperty("ResultItemList"))
    {
        var playerResultData = JSON.parse(playerTitleData.Data["ResultItemList"].Value);
    }
    
    return playerResultData.result_list;
}

// 生存期間計算
function CalcSurvivedDays()
{
    var latestItemList = [];
    var titleData = server.GetTitleData({
        "Keys":["Item"]
    });
    var playerData = server.GetUserReadOnlyData({
        "PlayFabId":currentPlayerId,
        "Keys":["ResultItemList"]
    });
    if(titleData.Data.hasOwnProperty("Item"))
    {
        var itemList = JSON.parse(titleData.Data["Item"]);    
    }
    if(playerData.Data.hasOwnProperty("ResultItemList"))
    {
        var allPlayerData = JSON.parse(playerData.Data["ResultItemList"].Value);
    }
    
    var itemObject = allPlayerData.result_list[0];
    for(var key in itemObject)
    {
        latestItemList.push(itemObject[key]);
    }
    
    // 生存期間計算
    var survivedDays = 0;
    var minuseDays = 0;
    for(var i = 0; i < 2; i++)
    {
        if(itemList.Items[latestItemList[i]].GenreID == 1)
        {
            survivedDays += itemList.Items[latestItemList[i]].Magnification * itemList.Items[latestItemList[i]].SpecialNo;
            survivedDays = Math.floor(survivedDays);
        }
        else
        {
            minuseDays += 7;
        }
    }
    for(var i = 2; i < 4; i++)
    {
        if(itemList.Items[latestItemList[i]].GenreID == i)
        {
            survivedDays += itemList.Items[latestItemList[i]].Magnification * itemList.Items[latestItemList[i]].SpecialNo;
            survivedDays = Math.floor(survivedDays);
        }
        else
        {
            minuseDays += 7;
        }
    }
    for(var i = 4; i <= 5; i++)
    {
        if(itemList.Items[latestItemList[i]].GenreID == 4)
        {
            survivedDays += itemList.Items[latestItemList[i]].Magnification * itemList.Items[latestItemList[i]].SpecialNo;
            survivedDays = Math.floor(survivedDays);
        }
        else
        {
            minuseDays += 7;
        }
    }
    if(itemList.Items[latestItemList[6]].GenreID == 5)
    {
        survivedDays += itemList.Items[latestItemList[6]].Magnification * itemList.Items[latestItemList[6]].SpecialNo;
        survivedDays = Math.floor(survivedDays);
    }
    else
    {
        minuseDays += 7;
    }
    
    survivedDays -= minuseDays;
    if(survivedDays < 0)
        survivedDays = 0;
    
    return survivedDays;
}

// リザルトテキスト取得
function CreateText()
{
    var food_1, food_2, drink, healing_goods, fuel, material;
    var resultText;
    var titleData = server.GetTitleData({
        "Keys":["Item"]
    });
    var playerData = server.GetUserReadOnlyData({
       "playFabId": currentPlayerId,
       "Keys":["ResultItemList"]
    });
    
    if(titleData.Data.hasOwnProperty("Item"))
    {
        var allItemDic = JSON.parse(titleData.Data["Item"]);
    }
    if(playerData.Data.hasOwnProperty("ResultItemList"))
    {
        var allPlayerData = JSON.parse(playerData.Data["ResultItemList"].Value);
    }
    else
    {
        return { Result : "プレイヤーデータがありません"}    
    }
    
    var itemList = allPlayerData.result_list[0];
    food_1 = allItemDic.Items[itemList.Item1].Name;
    food_2 = allItemDic.Items[itemList.Item2].Name;
    drink = allItemDic.Items[itemList.Item3].Name;
    healing_goods = allItemDic.Items[itemList.Item4].Name;
    fuel_1 = allItemDic.Items[itemList.Item5].Name;
    fuel_2 = allItemDic.Items[itemList.Item6].Name;
    material = allItemDic.Items[itemList.Item7].Name;

    resultText = `かくして・・・じんるいはチキュウを　だっしゅつした\n${food_1}　や　${food_2}　を　たべ\n${drink}　を　のみ\n${healing_goods}　で\nこころをいやした\n\nねんりょうの　${fuel_1}\nそして ${fuel_2}　を　もやしながら\n${material}　で　できたロケットは\nみなのキボウをのせて　とんでゆく・・・\n`;
    
    return { Result : resultText };
}

// Unityから渡されたアイテムリストをPlayerDataのTitleData(ReadOnly)に保存
// ハンドラーオブジェクト
handlers.SaveLatestResult = function (args)
{
    var ResultItemList = [];
    var playerData = server.GetUserReadOnlyData({
        "PlayFabId": currentPlayerId,
        "Keys":["ResultItemList"]
    });
    
    var allData = {};
    var dataPayload = {};
    var inputItemValue = null;
    if(args && args.inputValue)
        inputItemValue = args.inputValue;
    
    if(playerData.Data.hasOwnProperty("ResultItemList"))
    {
        allData = JSON.parse(playerData.Data["ResultItemList"].Value);
    }
    
    if(allData.hasOwnProperty("result_list"))
    {
        allData.result_list.unshift(JSON.parse(inputItemValue));
    }
    else
    {
        allData["result_list"] = [];
        allData.result_list.push(JSON.parse(inputItemValue));
    }
    
    while(allData.result_list.length > 5)
    {
        allData.result_list.pop();
    }
    
    dataPayload[args.keyName] = JSON.stringify(allData);
    
    var result = server.UpdateUserReadOnlyData({
        PlayFabId : currentPlayerId,
        Data : dataPayload,
        Permission : "public"
    });
}

```

</div>
</details>

# まとめ

- **PlayFabのAPIは連続で呼び出さないようにする**

以上、WebGLでPlayFabを使用するときの注意点でした。
そもそも連続で呼び出すような設計だったのが良くなかったのかもしれませんが・・・

自分が調べられてないだけで連続で呼び出す方法も存在するのかもしれません。

# 今後やりたいこと
現状だとPlayFabを使用した恩恵が全く得られていない(オンライン要素がない)ので
ログイン中のプレイヤーから他のプレイヤーのデータを取得してオンライン要素を追加したいです。

ですが、PlayFabのAPIで他のプレイヤーのTitleDataを取得するものがない・・・？
自分の調査不足かもしれません、公式フォーラムでもそのようなことをしている方が少ない印象でした。

現状思いつく方法は

1. **プレイヤーIDを取得**
2. **IDからプレイヤーのTitleDataを取得**
3. **データ処理**

ですが、この方法をCloudScriptで行おうとするとAPI制限値にすぐ引っかかってしまいそうです。
もう少しリファレンスを眺めたり、新しいEntityモデルと言う概念も勉強したりしようと思います。

# おわりに
ここまで読んでいただきたいありがとうございます。
はじめてPlayFabを触ったので間違っていることを書いているかもしれません。(PlayFabに限らずプログラムのことも)
WebGLでゲームを動かす仕様も付け焼刃な部分ばかりです。
その時はご指摘いただければ幸いです。

今回作ったゲームです。チームで制作しました。
よかったら遊んでください。
https://unityroom.com/games/aicando

# 参考資料リンク
- [PlayFab 各プランのAPI制限値とか比較してみた](https://qiita.com/naichilab/items/57fe8ad11cde0d4094e5)
- [PlayFab、GameManager（管理画面）上でCloudScriptを実行する](https://qiita.com/naichilab/items/71ade7a8eee22602150c)
- [UnityからPlayFabのCloudFunctionを呼び出してみる](https://qiita.com/naichilab/items/133d29ab0645ab669ac1)
- [UnityでPlayFabのタイトルデータを取得して表示する](https://qiita.com/_y_minami/items/3396f832d868d52bbbaf)
- [UnityからPlayFabのユーザーデータをCRUDする](https://qiita.com/_y_minami/items/5914b2ef89a8c83444b7)
- [個人のゲーム開発者がAzure PlayFabに入門してみた4つの理由](https://qiita.com/_y_minami/items/6baa0445c2b161aeec65)

PlayFab先駆者の皆様ありがとうございました。