はじめに
記事の紹介
こちらの記事ではUnityでVTuber配信してる際に、外部サービスのREST APIを叩く方法について紹介をします。Unity内からkintoneのREST APIを叩き、kintoneアプリ(データベース)に保存されてる情報を取得し、Unityの環境内に表示させます。この記事での使い方の例として、kintoneで『ネタ帳アプリ』を作成し、そのネタ帳に記録されてる内容をVTuber配信用のUnity画面に表示させます。
こちらの記事はEngineering for Vtuber Advent Calendar 2021 の16日目の記事になります。
自己紹介
自社イベントで自作アバターのForksちゃんに受肉した人です。公に活動してるVTuberではありません。kintone連携を作るのが好きなDeveloper Advocateです。
では早速、作り方に進みましょう。
kintone側の準備
kintoneアプリを作成する
まずkintoneでネタ帳アプリを作成します。新しいアプリを作成し、文字列(1行)フィールド、ラジオボタンフィールド、文字列(複数行)フィールドをそれぞれ下記のようにフォームに配置します。
「アプリを公開」をクリックすると、環境内でアプリを使えるようになります。
レコードを追加する
作りたてのアプリにはデータが入っていません。GUIを通してレコードを追加しましょう。
APIトークンを生成する
kintoneの環境外から、このアプリのデータを取得出来るように準備します。アプリからAPIトークンを生成すると、認証に使うトークン文字列が発行されます。レコードの閲覧条件があるAPIトークンを生成しましょう。
これでkintone側の準備が出来ました。これから、Unity内からこのデータをアクセスして画面に表示させます。
Unity側の準備
VTuber環境の作成
VTuber用の環境をUnityで作成します。UnityとiFacialMocapを利用したVTuber環境の作成について記事を書きましたので、この記事の通り準備を進め、ビルド手前でさらに手を加えたいと思います。
Textオブジェクトの追加
kintoneから取得した情報を表示する場所が準備する必要があります。Hierarchy内に右クリックして、UI → Text と選択し、Textオブジェクトをシーンに追加します。TextオブジェクトのHorizontal Overflow と Vertical Overflow を Overflow に設定すると、取得した文字列が長くても、見切れなくなります。
SimpleJSONの準備
UnityからREST APIを叩く場合、SimpleJSONというフレームワークを使用するとJSONの扱いが楽になります。SimpleJSONの準備についてはこちらの記事で案内してます。この記事と同じ用に、SimpleJSONのライブラリをpluginsフォルダに配置します。
コードの準備
kintoneからデータを取得するためのC#スクリプトを書きます。このスクリプトは適当なオブジェクトに貼ってOKです。今回の例ではスペースキーをタップすると、kintoneのレコードの一括取得APIが叩かれ、レスポンスされた内容がTextオブジェクトに表示されます。
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オブジェクトに反映されます。
スペースキーをもう一回押すと、REST APIが再度叩かれ、Textオブジェクトが更新されます。最初に表示されていた情報のステータスが「検討中」から「採用」に更新されていた場合、次のレコードが拾われて表示されます(「検討中」のレコードしか拾わないため)。
また、GUIでレコードの中のテキストを編集し、Unityに戻ってスペースキーを押したら、その修正が反映されます。
コードの解説
定数/変数の定義
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, query と fields を引数として実行されます。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オブジェクトをうまく利用し、複数のフィールドの内容が見えるようなフォームを作りましょう。その上にレスポンスの情報を表示することによって、コンテンツが見やすくなります。
アプリのデータの扱いを工夫する
今回の例では情報の取得をしてますが、kintoneのAPIは色々なAPIが準備されています。見せたいコンテンツに合わせて使い分けて行きましょう。
REST APIを実行する方法を工夫する
今回の例では、スペースキーをトリガーにREST APIを叩きました。エンドユーザに見えるように、ボタンのクリックをしてREST APIを叩くように出来ます。下記の例ではボタンクリック毎にレコードの一括取得をしていますが、レスポンスの中のx番目の配列データを表示するようにしてます。
また、アバターの身体のパーツの位置や回転の値を取得し、閾値を超えたらREST APIを叩くことも出来ます。下記の例ではスマイルによってレコードの情報を更新してます。
手の動きの検出が出来たら、さらにスムーズに表現が出来そうですね。
おわりに
お付き合いありがとうございました٩( 'ω' )و
もしUnityからREST APIを叩くことに興味が出てきましたら、下記の記事も参考になると思います: