kintoneアプリに溜めたゲームスコア情報をUnity内に表示させる

# はじめに
Unityのゲーム内から、kintone環境のアプリ(ウェブデータベース)のレコード情報をREST APIで取得する方法を案内します。

この記事の例では、ゲームスコアを記録するkintoneアプリからデータを取得し、Unity内のオブジェクトにそのスコアリストのデータを反映させます。

![連携イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/8e107f11-fb42-d19e-169f-73919b09f640.png)


JSONを簡単に取り扱うためにSimpleJSONを使用します。
SimpleJSONの準備に関しては下記を参考にしてください：
https://qiita.com/will-yama/items/19078a905ef4207977e4

# kintoneアプリの準備
まずゲームスコアを記録するアプリを作りましょう。
kintone環境を持っていない方はcybozu developer networkの[このページ](https://developer.cybozu.io/hc/ja/articles/200720464)から1年間無料で使用できる開発者ライセンスのkintone環境を手に入れてください。

## フィールドの設定
プレイヤー名とスコアを記録するために、**文字列(1行)**と**数値**フィールドを配置します。
それぞれの設定画面で適当なフィールド名と、*playername*と*score*というフィールドコードを設定します。このフィールドコードは後ほどREST APIを叩くときに必要になります。
![scoreApp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/38115f26-a355-0c46-4619-b1a6f3255611.jpeg)


フォームの保存をしたら、『設定』タブをクリックします。

## APIトークンの設定
『APIトークン』をクリックし、[この記事の通り](https://developer.cybozu.io/hc/ja/articles/202463840)にAPIトークンを生成します。アクセス権はレコードの閲覧だけで大丈夫です。APIトークンを生成したら、設定の保存をします。

## アプリの公開
アプリがkintoneユーザに利用できる状態にするために、『アプリを公開』ボタンをクリックします。すでに一度アプリの公開をしている場合、ボタンは『アプリを更新』と表示されます。とりあえず、ボタンを押しましょう。

## データの追加
アプリの公開が出来たら、レコードの一覧画面に移動します。
データが入っていない状態なので、＋ボタンをクリックして適当なダミーデータを追加しましょう。
<img width="906" alt="Screen Shot 2019-11-26 at 20.10.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/7707d04a-27f0-aece-fbc5-2fb0f576ec3b.png">

ダミーデータを入力し終わった画面がこちらです。
<img width="953" alt="Screen Shot 2019-11-26 at 21.15.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/6301ed19-df0c-c150-d10f-80368172e2a6.png">


# Unityからkintoneアプリのデータを取得する

Unityの適当なオブジェクトにスクリプトを追加しましょう。
スクリプトからkintoneにアクセスする方法を案内します。

## レコードを複数取得する

まず例として、全てのレコードを取得するコードです。
**{サブドメイン名}**、**{APIトークン}**と**{アプリID}**は適宜自分のkintone環境に合わせて設定してください。

```testAPI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using SimpleJSON;

public class testAPI : MonoBehaviour
{
    const string domain = "{サブドメイン名}.cybozu.com";
    const string APItoken = "{APIトークン}";

    void Start()
    {
        string app = "{アプリID}";
        string query = System.Uri.EscapeDataString("order by playername desc");
        string fields = System.Uri.EscapeDataString("fields[0]") + "=" + System.Uri.EscapeDataString("$id")
        + "&" + System.Uri.EscapeDataString("fields[1]") + "=" + System.Uri.EscapeDataString("score")
        + "&" + System.Uri.EscapeDataString("fields[2]") + "=" + System.Uri.EscapeDataString("playername");

        StartCoroutine(
            getKintoneRecords(
                (JSONNode JSONresponse) => 
                {
                    HandleAPIresponse(JSONresponse);
                }, 
                app, 
                query, 
                fields
            )
        );
    }

    private IEnumerator getKintoneRecords(System.Action<JSONNode> callBack, string app, string query, string fields)
    {
        //APIリクエストの準備
        string APIparameters = "app=" + app + "&query=" + query + "&" + fields + "&totalCount=true";
        string RequestURL = "https://" + domain + "/k/v1/records.json?" + APIparameters;
        UnityWebRequest request = UnityWebRequest.Get(RequestURL);
        request.SetRequestHeader("X-Cybozu-API-Token", APItoken);

        //APIのリクエストを送信
        yield return request.SendWebRequest();

        //レスポンスをJSONNode型に格納
        string JSONstring = request.downloadHandler.text;
        Debug.Log("JSONstring--> " + JSONstring);
        JSONNode JSONnode = JSON.Parse(JSONstring);

        //JSONNode型のレスポンスをcallBackに渡す
        callBack(JSONnode);
    }

    private void HandleAPIresponse(JSONNode APIresponse)
    {
        //レスポンスを用いた処理
    }
}
```

レスポンスされるJSONの中身はこのような形になります。

```json
{ 
   "records":[ 
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"302544"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"ojisan man"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"7"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"304267"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"ojisan man"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"2"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"331928"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"kerokero_B"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"6"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"294357"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"kero99powers"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"3"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"312555"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"ken99"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"5"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"340232"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"Unicorn_Z"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"9"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"300042"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"Unicorn_Z"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"1"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"305288"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"Magical Piano"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"10"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"255522"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"JJ88"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"8"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"244302"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"JJ88"
         },
         "$id":{ 
            "type":"__ID__",
            "value":"4"
         }
      }
   ],
   "totalCount":"10"
}
```

このリクエストでは*query*パラメータで名前順でレコード情報を取得するようにしました。queryパラメータを無視する場合、レコードID順に返ってきます。
*fields*パラメータにはJSONの中に含めて欲しいフィールドの情報を指定します。このパラメータを無視する場合、各レコードに対してかなり多くのフィールド情報が返ってくるので、JSONの読みやすさのためにも指定することをお勧めします。ちなみにですが、*fields*パラメータの中で指定した *$id* はレコード番号のフィールドを指しています。レコード番号のフィールドコードを代わりに記載しても同じ動きをします。


## クエリを工夫する

スコアリストのデータを扱うので、下記の条件でレコード情報を取得したいと思います：
- スコアが高い順
- Top 3のみのスコア
- プレイヤー名とスコアの情報

**Start()**内のクエリをこのように変更します:

```cs
void Start()
{
    string app = "12";
    string query = System.Uri.EscapeDataString("order by score desc limit 3");
    string fields = System.Uri.EscapeDataString("fields[0]") + "=" + System.Uri.EscapeDataString("playername")
    + "&" + System.Uri.EscapeDataString("fields[1]") + "=" + System.Uri.EscapeDataString("score");  

    StartCoroutine(
        getKintoneRecords(
            (JSONNode JSONresponse) => 
            {
                HandleAPIresponse(JSONresponse);
            }, 
            app, 
            query, 
            fields
        )
    );
}
```

レスポンスのJSONは下記の通りになります：

```json
{ 
   "records":[ 
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"340232"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"Unicorn_Z"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"331928"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"kerokero_B"
         }
      },
      { 
         "score":{ 
            "type":"NUMBER",
            "value":"312555"
         },
         "playername":{ 
            "type":"SINGLE_LINE_TEXT",
            "value":"ken99"
         }
      }
   ],
   "totalCount":"10"
}
```

*order by score desc* で JSON内のレコード情報がスコアの値の降順で並びます。
*limit 3* で、最初の3つのレコードの情報だけJSONに含まれるようにします。

# オブジェクトへの応用

最後に、このデータをUnity内のオブジェクトに応用します。

## オブジェクトの準備

まず、テキスト情報が調整しやすい下記の無料アセットを使います:

**Menu Chalk Board**
https://assetstore.unity.com/packages/3d/characters/menu-chalk-board-101989
<img width="1325" alt="screenshot of the Menu Chalk Board asset found on the Unity Asset Store" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/a3034c88-d94c-093d-625a-daaa8d15db61.png">

**menuboard**のprefabをシーンに入れます。
アセット内の**Canvas**配下の**Title**と**Menu**オブジェクトの詳細はインスペクタからいじることが可能です。
**Title**オブジェクトは好みの内容に変え、**Menu**オブジェクト内のテキスト情報は全て消しましょう。デフォルトのテキストが少し読みにくかったので、私は下記のように設定をしました：
<img width="291" alt="Screen Shot 2019-11-27 at 11.05.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/1b7d28d1-c27b-6048-815e-a608aa1aa17d.png">

## スクリプトの準備

この記事で活用していたスクリプトは適当なオブジェクトに追加していたと思いますが、**menuboard**の**Canvas**オブジェクトに移動させましょう。

また、テキストの内容をスクリプトでいじるため、

```cs
using UnityEngine.UI;
```
をスクリプトの最初の方で宣言します。

最後に、まだ処理を記載していなかった*HandleAPIresponse*関数内に下記の内容を追記します。

```cs
private void HandleAPIresponse(JSONNode APIresponse)
{
    //kintoneアプリのレコード情報を変数に格納
    string FirstPlayersName = APIresponse["records"][0]["playername"]["value"].Value;
    string FirstPlayersScore = APIresponse["records"][0]["score"]["value"].Value;
    string SecondPlayersName = APIresponse["records"][1]["playername"]["value"].Value;
    string SecondPlayersScore = APIresponse["records"][1]["score"]["value"].Value;
    string ThirdPlayersName = APIresponse["records"][2]["playername"]["value"].Value;
    string ThirdPlayersScore = APIresponse["records"][2]["score"]["value"].Value;

    //『Menu』の名前を持つ子オブジェクトのTextコンポーネントの内容を上書きする
    GameObject CanvasMenu = this.gameObject.transform.Find("Menu").gameObject;
    Text CanvasMenuText = CanvasMenu.GetComponent<Text>();
    CanvasMenuText.text = "1. " + FirstPlayersName + "   " + FirstPlayersScore + "p\n"
                        + "2. " + SecondPlayersName + "   " + SecondPlayersScore + "p\n"
                        + "3. " + ThirdPlayersName + "   " + ThirdPlayersScore + "p";
}
```

シーンをplayしてみて、下記のようにスコア情報がkintoneから取得出来たら成功です。
<img width="852" alt="Screen Shot 2019-11-27 at 11.09.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/9791054a-c01d-2d89-5def-72f7e1e72f72.png">