VTuberの配信環境でREST APIを叩く方法

## はじめに
### 記事の紹介
こちらの記事ではUnityでVTuber配信してる際に、外部サービスのREST APIを叩く方法について紹介をします。Unity内からkintoneのREST APIを叩き、kintoneアプリ(データベース)に保存されてる情報を取得し、Unityの環境内に表示させます。この記事での使い方の例として、kintoneで『ネタ帳アプリ』を作成し、そのネタ帳に記録されてる内容をVTuber配信用のUnity画面に表示させます。

![intro_pic2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/9bef3beb-e856-a0d8-8e1a-e438f547673f.jpeg)

こちらの記事は[Engineering for Vtuber Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vtuber) の16日目の記事になります。

### 自己紹介
自社イベントで自作アバターのForksちゃんに受肉した人です。公に活動してるVTuberではありません。kintone連携を作るのが好きなDeveloper Advocateです。

では早速、作り方に進みましょう。

## kintone側の準備
### kintoneアプリを作成する
まずkintoneでネタ帳アプリを作成します。[新しいアプリを作成](https://jp.cybozu.help/k/ja/user/create_app/tutorial.html)し、**文字列(1行)フィールド**、**ラジオボタンフィールド**、**文字列(複数行)フィールド**をそれぞれ下記のようにフォームに配置します。
![kintone_formbuilder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/a3cfbe6c-5c0b-2307-e684-e707eab8c343.png)

「アプリを公開」をクリックすると、環境内でアプリを使えるようになります。

### レコードを追加する
作りたてのアプリにはデータが入っていません。GUIを通して[レコードを追加](https://jp.cybozu.help/k/ja/user/using_app/add_record.html)しましょう。
![kintone_recordlist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/fa36e45e-8028-7b77-c130-efa899b11d3b.png)

### APIトークンを生成する
kintoneの環境外から、このアプリのデータを取得出来るように準備します。アプリから[APIトークンを生成](https://jp.cybozu.help/k/ja/id/040471.html#app_settings_api_token_10)すると、認証に使うトークン文字列が発行されます。レコードの閲覧条件があるAPIトークンを生成しましょう。

これでkintone側の準備が出来ました。これから、Unity内からこのデータをアクセスして画面に表示させます。

## Unity側の準備
### VTuber環境の作成
VTuber用の環境をUnityで作成します。[UnityとiFacialMocapを利用したVTuber環境の作成](https://qiita.com/will-yama/items/d348db26d9309107ba31)について記事を書きましたので、この記事の通り準備を進め、ビルド手前でさらに手を加えたいと思います。

### Textオブジェクトの追加
kintoneから取得した情報を表示する場所が準備する必要があります。Hierarchy内に右クリックして、**UI** → **Text** と選択し、Textオブジェクトをシーンに追加します。Textオブジェクトの**Horizontal Overflow** と **Vertical Overflow** を *Overflow* に設定すると、取得した文字列が長くても、見切れなくなります。

### SimpleJSONの準備
UnityからREST APIを叩く場合、SimpleJSONというフレームワークを使用するとJSONの扱いが楽になります。SimpleJSONの準備については[こちらの記事](https://qiita.com/will-yama/items/19078a905ef4207977e4)で案内してます。この記事と同じ用に、SimpleJSONのライブラリを**plugins**フォルダに配置します。

### コードの準備
kintoneからデータを取得するためのC#スクリプトを書きます。このスクリプトは適当なオブジェクトに貼ってOKです。今回の例ではスペースキーをタップすると、kintoneの[レコードの一括取得API](https://developer.cybozu.io/hc/ja/articles/202331474#step2)が叩かれ、レスポンスされた内容がTextオブジェクトに表示されます。

```KintoneDataController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using SimpleJSON;
using UnityEngine.UI;

public class kintoneDataController : MonoBehaviour
{
    const string domain = "{サブドメイン名}.cybozu.com";
    const string APItoken = "fOp04Sdfodsok32rFsdofkASdsodkDFdfkok";
    string app = "212";
    string query = System.Uri.EscapeDataString("hyouka in (\"検討中\") order by $id desc");
    string fields = System.Uri.EscapeDataString("fields[0]") + "=" + System.Uri.EscapeDataString("$id")
    + "&" + System.Uri.EscapeDataString("fields[1]") + "=" + System.Uri.EscapeDataString("neta")
    + "&" + System.Uri.EscapeDataString("fields[2]") + "=" + System.Uri.EscapeDataString("hyouka")
    + "&" + System.Uri.EscapeDataString("fields[3]") + "=" + System.Uri.EscapeDataString("shousai");

    void Start()
    {

    }

    void Update()
    {
      if (Input.GetKeyDown(KeyCode.Space))
      {
        Debug.Log("Space pressed");
        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)
    {
        Text ResponseHolder = GameObject.Find("DisplayText").GetComponent<Text>();

        string Text_Hyouka = APIresponse["records"][0]["hyouka"]["value"].Value;
        string Text_Neta = APIresponse["records"][0]["neta"]["value"].Value;
        string Text_Shousai = APIresponse["records"][0]["shousai"]["value"].Value;
        ResponseHolder.text = "[ステータス] " + Text_Hyouka + "\n"
                            + "[ネタ] " + Text_Neta + "\n"
                            + "[詳細] " + Text_Shousai;
    }
}
```

## 動かしてみる
ではさっそくUnity上で動かしてみましょう。
スペースキーを押すと、レコードの一括取得APIが叩かれます。kintoneアプリに入ってるレコードの中で、「検討中」となってるレコードが複数取得されます。その中でレコード番号が一番大きいレコード(最新のレコード)の情報がUnity上のTextオブジェクトに反映されます。
![copiedrecorddata.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/71c7d2a2-61da-ad10-06de-9be4ac6276f4.jpeg)

スペースキーをもう一回押すと、REST APIが再度叩かれ、Textオブジェクトが更新されます。最初に表示されていた情報のステータスが「検討中」から「採用」に更新されていた場合、次のレコードが拾われて表示されます(「検討中」のレコードしか拾わないため)。
![changethestatus.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/41374bde-ea1d-8112-5d39-9207aa491f2b.gif)


また、GUIでレコードの中のテキストを編集し、Unityに戻ってスペースキーを押したら、その修正が反映されます。

![updateracord2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/ea3efa57-7c02-7f64-df65-2044b015172e.gif)


## コードの解説

### 定数/変数の定義
```cs
    const string domain = "{サブドメイン名}.cybozu.com";
    const string APItoken = "fOp04Sdfodsok32rFsdofkASdsodkDFdfkok";
    string app = "212";
    string query = System.Uri.EscapeDataString("hyouka in (\"検討中\") order by $id desc");
    string fields = System.Uri.EscapeDataString("fields[0]") + "=" + System.Uri.EscapeDataString("$id")
    + "&" + System.Uri.EscapeDataString("fields[1]") + "=" + System.Uri.EscapeDataString("neta")
    + "&" + System.Uri.EscapeDataString("fields[2]") + "=" + System.Uri.EscapeDataString("hyouka")
    + "&" + System.Uri.EscapeDataString("fields[3]") + "=" + System.Uri.EscapeDataString("shousai");

```
まずはREST APIを叩くために必要な情報を定義します。こちらの定数や変数はグローバルで定義しない方が良いですが、コードの読みやすさを重視して書いてるので、お許しください。各定数/変数の役割は下記の通りです：

| 定数 / 変数 | 役割 |
|:-:|:-|
|  domain | kintoneアプリが作られた環境のドメイン名です。**{サブドメイン名}** の部分は環境によって違います。サブドメイン名は第三者と共有しないように気をつけましょう。 |
| APItoken  |  認証に必要なトークン文字列です。このトークンは特定のアプリにしかアクセス出来ません。APItokenは第三者と共有しないように気をつけましょう。 |
| app  | REST APIを使って情報を取得したいkintoneアプリのIDです。ブラウザからアプリにアクセスした際に、URLからアプリIDが確認が出来ます。リンクが **https://{サブドメイン名}.cybozu.com/k/212/** の場合、アプリIDは212です。 |
| query  | REST APIに使用するクエリ文字列です。今回使用するAPIはレコード一括取得のAPIです。クエリを指定することによって、条件付きでレコードの取得をすることが出来ます。クエリの内容は『hyoukaの[フィールドコード](https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/fieldcode.html)をもつフィールドの値が【検討中】となっているレコードの情報を取得し、レコードIDの降順に並び替える』です。  |
| fields  | REST APIのレスポンスに含めたいフィールドの情報です。指定しない場合、レスポンスには全てのフィールドの情報が含まれ、大変膨大な内容になってしまいます。レスポンスされるフィールドを絞ることによって、人間にもデバッグしやすいようにします。フィールドを指定する際には、フィールドのフィールド名ではなく、[フィールドコード](https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/fieldcode.html)を指定します。今回予め、取得したいフィールドのフィールドコードをそれぞれ "neta", "hyouka" と "shousai" に設定してます。  |

### ユーザインプットの処理
```cs
    void Update()
    {
      if (Input.GetKeyDown(KeyCode.Space))
      {
        Debug.Log("Space pressed");
        StartCoroutine(
            getKintoneRecords(
                (JSONNode JSONresponse) =>
                {
                    HandleAPIresponse(JSONresponse);
                },
                app,
                query,
                fields
            )
        );
      }
    }
```
ユーザがキーボードのスペースキーを押すことによって、REST APIを叩くようにしています。REST APIは非同期処理のため、コルーチンで処理する必要があります。まず **getKintoneRecords** の関数が **app**, **query** と **fields** を引数として実行されます。**getKintoneRecords** 内ではREST APIの処理がされます。レスポンスが返ってきたら、レスポンスの内容は **JSONresponse** という変数に渡され、**HandleAPIresponse** の関数が走ります。

### kintone REST APIを叩く処理
```cs
    private IEnumerator getKintoneRecords(System.Action<JSONNode> callBack, string app, string query, string fields)
    {

    }
```
StartCorutine で呼ばれる関数は **private IEnumerator** で宣言する必要があります。

```cs
        //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);
```
REST API を叩くための情報を準備します。リクエストURLとして下記のURLを作成してます。


```
https://{サブドメイン名}.cybozu.com/k/v1/records.json?app=212&query=hyouka in ("検討中") order by $id desc&fields[0]=$id&fields[1]=neta&fields[2]=hyouka&fields[3]=shousai&totalCount=true
```

レコードの一括取得APIに付与出来るパラメータの詳細についてはkintoneの[APIドキュメント](https://developer.cybozu.io/hc/ja/articles/202331474#step2)で確認できます。



```cs
        //レスポンスをJSONNode型に格納
        string JSONstring = request.downloadHandler.text;
        Debug.Log("JSONstring--> " + JSONstring);
        JSONNode JSONnode = JSON.Parse(JSONstring);
```
SimpleJSONで定義されているJSONNode型で変数を定義し、REST APIのJSONレスポンスを格納します。ここで一番何かしらのエラーが出る場合が多いので、デバッグ用にコンソールに吐き出すようにしてます。JSONが吐き出されていれば成功ですが、失敗の場合はREST APIに必要な情報に過不足がある場合が多いです。

```cs
        //JSONNode型のレスポンスをcallBackに渡す
        callBack(JSONnode);
```
次に走らせる関数に渡したい情報を設定します。この場合、レスポンスされたJSONを渡します。



### REST APIのレスポンスをハンドリングする処理
```cs
    private void HandleAPIresponse(JSONNode APIresponse)
    {

    }
```
この関数の中で、レスポンスされたJSONを扱います。ここまでくれば何をしても良いのですが、今回の例ではTextオブジェクトをJSON内の値で更新します。

```cs
        Text ResponseHolder = GameObject.Find("DisplayText").GetComponent<Text>();
```
内容を更新したいTextオブジェクトを探し、textコンポーネントを取得します。textコンポーネントを変更することにより、表示されるテキストを変更することが出来ます。

```cs
        string Text_Hyouka = APIresponse["records"][0]["hyouka"]["value"].Value;
        string Text_Neta = APIresponse["records"][0]["neta"]["value"].Value;
        string Text_Shousai = APIresponse["records"][0]["shousai"]["value"].Value;
        ResponseHolder.text = "[ステータス] " + Text_Hyouka + "\n"
                            + "[ネタ] " + Text_Neta + "\n"
                            + "[詳細] " + Text_Shousai;
```


Textオブジェクトに表示される文字列を設定します。レスポンスされたJSONから必要な情報を抜き出し、文字列に加えます。JSONレスポンスの中身は下記の用になっています。

```json
{
   "records":[
      {
         "neta":{
            "type":"SINGLE_LINE_TEXT",
            "value":"バーチャルマーケットの紹介"
         },
         "shousai":{
            "type":"MULTI_LINE_TEXT",
            "value":"現在と過去のバーチャルマーケットについての紹介。\n初心者向けにブースなどを案内する。"
         },
         "hyouka":{
            "type":"RADIO_BUTTON",
            "value":"検討中"
         },
         "$id":{
            "type":"__ID__",
            "value":"5"
         }
      },
      {
         "neta":{
            "type":"SINGLE_LINE_TEXT",
            "value":"Maker Faireの紹介"
         },
         "shousai":{
            "type":"MULTI_LINE_TEXT",
            "value":"全国で行われているMaker Faireについての紹介。\n余裕があれば、世界中で行われているMaker Faireの紹介もする。国によってメイカーが作る作品の傾向が変わっており、結構面白い。"
         },
         "hyouka":{
            "type":"RADIO_BUTTON",
            "value":"検討中"
         },
         "$id":{
            "type":"__ID__",
            "value":"4"
         }
      },
      {
         "neta":{
            "type":"SINGLE_LINE_TEXT",
            "value":"レトロゲーム配信 - 初代熱血硬派くにおくん "
         },
         "shousai":{
            "type":"MULTI_LINE_TEXT",
            "value":"スーファミの「初代熱血硬派くにおくん」の実況プレイ。\n大阪って色々な人に絡まれて怖いね！ってのをみんなに体験してもらいたい。"
         },
         "hyouka":{
            "type":"RADIO_BUTTON",
            "value":"検討中"
         },
         "$id":{
            "type":"__ID__",
            "value":"3"
         }
      },
      {
         "neta":{
            "type":"SINGLE_LINE_TEXT",
            "value":"VRoid Studio 正式リリース版 基本機能紹介"
         },
         "shousai":{
            "type":"MULTI_LINE_TEXT",
            "value":"VRoid Studio 正式リリースおめでとう！\nということで、GUIも色々と変わったところがあります。\nおなじみの機能、新しい機能、それぞれ確認しながら説明していく。"
         },
         "hyouka":{
            "type":"RADIO_BUTTON",
            "value":"検討中"
         },
         "$id":{
            "type":"__ID__",
            "value":"2"
         }
      },
      {
         "neta":{
            "type":"SINGLE_LINE_TEXT",
            "value":"ロックマンX6配信 - セイバー縛り"
         },
         "shousai":{
            "type":"MULTI_LINE_TEXT",
            "value":"ロックマンX6でロックマンでクリアしていく。\n縛り要素として、ロックバスター禁止でセイバーのみで進める。どうしてもセイバーで進むことが無理な箇所はロックバスターを解禁してもよし。"
         },
         "hyouka":{
            "type":"RADIO_BUTTON",
            "value":"検討中"
         },
         "$id":{
            "type":"__ID__",
            "value":"1"
         }
      }
   ],
   "totalCount":"5"
```

オブジェクトの階層を掘って、`APIresponse["records"][0]["hyouka"]["value"].Value` のように最後に `.Value` と指定することによって、その値を文字列として抜き出すことができます。

以上で、UnityのVTuber環境からkintoneの情報を抜き出して表示させる方法を紹介しました。

## 少し映える工夫をする
今回は余計なノイズが入らないように、出来るだけシンプルな実装にしました。結果、取得した情報がわりと地味に表示されました。私が社内イベントで活用した、少し映えるような工夫を紹介します。

### 情報が表示される場所を工夫する
まずこれですね。UnityのCanvasオブジェクトをうまく利用し、複数のフィールドの内容が見えるようなフォームを作りましょう。その上にレスポンスの情報を表示することによって、コンテンツが見やすくなります。
![form.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/e584b79a-5c34-48f3-eba8-c0afd48b9127.jpeg)



### アプリのデータの扱いを工夫する
今回の例では情報の取得をしてますが、kintoneのAPIは色々なAPIが準備されています。見せたいコンテンツに合わせて使い分けて行きましょう。

- [レコードの取得](https://developer.cybozu.io/hc/ja/articles/202331474)
- [レコードの登録](https://developer.cybozu.io/hc/ja/articles/202166160)
- [レコードの更新](https://developer.cybozu.io/hc/ja/articles/201941784)
- [ファイルダウンロード](https://developer.cybozu.io/hc/ja/articles/202166180)

### REST APIを実行する方法を工夫する
今回の例では、スペースキーをトリガーにREST APIを叩きました。エンドユーザに見えるように、ボタンのクリックをしてREST APIを叩くように出来ます。下記の例ではボタンクリック毎にレコードの一括取得をしていますが、レスポンスの中のx番目の配列データを表示するようにしてます。
![buttons2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/97074524-7102-2df4-5ecf-44a11ee17676.gif)


また、アバターの身体のパーツの位置や回転の値を取得し、閾値を超えたらREST APIを叩くことも出来ます。下記の例ではスマイルによってレコードの情報を更新してます。
![smileshounin2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/24183a2a-b624-3385-fb71-2cd679cdd530.gif)

手の動きの検出が出来たら、さらにスムーズに表現が出来そうですね。

## おわりに
お付き合いありがとうございました٩( 'ω' )و
もしUnityからREST APIを叩くことに興味が出てきましたら、下記の記事も参考になると思います:
- [kintoneに登録されたデータからUnityのゲームオブジェクトを生成する方法](https://qiita.com/will-yama/items/41b0962ceae6c7281569)
- [kintoneアプリに溜めたゲームスコア情報をUnity内に表示させる](https://qiita.com/will-yama/items/489b1f411e59bc95af26)
- [Cybozu Developer Network](https://developer.cybozu.io/hc/ja)