4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Engineering for VtuberAdvent Calendar 2021

Day 16

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

Last updated at Posted at 2021-12-15

はじめに

記事の紹介

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

intro_pic2.jpg

こちらの記事はEngineering for Vtuber Advent Calendar 2021 の16日目の記事になります。

自己紹介

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

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

kintone側の準備

kintoneアプリを作成する

まずkintoneでネタ帳アプリを作成します。新しいアプリを作成し、文字列(1行)フィールドラジオボタンフィールド文字列(複数行)フィールドをそれぞれ下記のようにフォームに配置します。
kintone_formbuilder.png

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

レコードを追加する

作りたてのアプリにはデータが入っていません。GUIを通してレコードを追加しましょう。
kintone_recordlist.png

APIトークンを生成する

kintoneの環境外から、このアプリのデータを取得出来るように準備します。アプリからAPIトークンを生成すると、認証に使うトークン文字列が発行されます。レコードの閲覧条件があるAPIトークンを生成しましょう。

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

Unity側の準備

VTuber環境の作成

VTuber用の環境をUnityで作成します。UnityとiFacialMocapを利用したVTuber環境の作成について記事を書きましたので、この記事の通り準備を進め、ビルド手前でさらに手を加えたいと思います。

Textオブジェクトの追加

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

SimpleJSONの準備

UnityからREST APIを叩く場合、SimpleJSONというフレームワークを使用するとJSONの扱いが楽になります。SimpleJSONの準備についてはこちらの記事で案内してます。この記事と同じ用に、SimpleJSONのライブラリをpluginsフォルダに配置します。

コードの準備

kintoneからデータを取得するためのC#スクリプトを書きます。このスクリプトは適当なオブジェクトに貼ってOKです。今回の例ではスペースキーをタップすると、kintoneのレコードの一括取得APIが叩かれ、レスポンスされた内容が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

スペースキーをもう一回押すと、REST APIが再度叩かれ、Textオブジェクトが更新されます。最初に表示されていた情報のステータスが「検討中」から「採用」に更新されていた場合、次のレコードが拾われて表示されます(「検討中」のレコードしか拾わないため)。
changethestatus.gif

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

updateracord2.gif

コードの解説

定数/変数の定義

    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のフィールドコードをもつフィールドの値が【検討中】となっているレコードの情報を取得し、レコードIDの降順に並び替える』です。
fields REST APIのレスポンスに含めたいフィールドの情報です。指定しない場合、レスポンスには全てのフィールドの情報が含まれ、大変膨大な内容になってしまいます。レスポンスされるフィールドを絞ることによって、人間にもデバッグしやすいようにします。フィールドを指定する際には、フィールドのフィールド名ではなく、フィールドコードを指定します。今回予め、取得したいフィールドのフィールドコードをそれぞれ "neta", "hyouka" と "shousai" に設定してます。

ユーザインプットの処理

    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, queryfields を引数として実行されます。getKintoneRecords 内ではREST APIの処理がされます。レスポンスが返ってきたら、レスポンスの内容は JSONresponse という変数に渡され、HandleAPIresponse の関数が走ります。

kintone REST APIを叩く処理

    private IEnumerator getKintoneRecords(System.Action<JSONNode> callBack, string app, string query, string fields)
    {

    }

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

        //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ドキュメントで確認できます。

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

SimpleJSONで定義されているJSONNode型で変数を定義し、REST APIのJSONレスポンスを格納します。ここで一番何かしらのエラーが出る場合が多いので、デバッグ用にコンソールに吐き出すようにしてます。JSONが吐き出されていれば成功ですが、失敗の場合はREST APIに必要な情報に過不足がある場合が多いです。

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

次に走らせる関数に渡したい情報を設定します。この場合、レスポンスされたJSONを渡します。

REST APIのレスポンスをハンドリングする処理

    private void HandleAPIresponse(JSONNode APIresponse)
    {

    }

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

        Text ResponseHolder = GameObject.Find("DisplayText").GetComponent<Text>();

内容を更新したいTextオブジェクトを探し、textコンポーネントを取得します。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;

Textオブジェクトに表示される文字列を設定します。レスポンスされた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

アプリのデータの扱いを工夫する

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

REST APIを実行する方法を工夫する

今回の例では、スペースキーをトリガーにREST APIを叩きました。エンドユーザに見えるように、ボタンのクリックをしてREST APIを叩くように出来ます。下記の例ではボタンクリック毎にレコードの一括取得をしていますが、レスポンスの中のx番目の配列データを表示するようにしてます。
buttons2.gif

また、アバターの身体のパーツの位置や回転の値を取得し、閾値を超えたらREST APIを叩くことも出来ます。下記の例ではスマイルによってレコードの情報を更新してます。
smileshounin2.gif

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

おわりに

お付き合いありがとうございました٩( 'ω' )و
もしUnityからREST APIを叩くことに興味が出てきましたら、下記の記事も参考になると思います:

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?