🌎

Cluster Creator Kitに実装された「外部通信機能」でなにができる?

2023/12/26に公開

https://qiita.com/advent-calendar/2023/iwakenlab
この記事は、「Iwaken Lab. Advent Calendar 2023 (シリーズ2)」24日の記事です。

Cluster Creator Kit 2.7.0で外部通信機能がベータ機能としてリリース

ベータ機能について

ベータ機能は正式リリース前のフィードバックを目的とした、正式リリース前の機能を試すための機能です。
ベータ機能の使用により、動作が不安定になったり、仕様変更により動作しなくなる可能性があることを理解したうえで使用する必要があります。
https://docs.cluster.mu/creatorkit/beta/

ベータ機能の制限

ベータ機能としてリリースされている機能を使用するには、いくつかの制限があります。

  • ワールドの初回アップロード時点で、ベータ機能が有効にしておく必要がある。
    • ベータ機能を使用するワールドは、初回アップロード時にCreator Kitの設定から、ベータ機能を有効にしておく必要があります。
      すでにアップロードしたワールドでベータ機能を有効にしたり、すでにベータ機能が有効なワールドを後から無効にすることができない点に注意してください。
    • ベータ機能が有効なワールドでは、入室するときにベータ機能が有効であることを示す警告が表示され、ワールドタイトルにアイコンが表示されます。
    • ワールドクラフトでも、ベータ機能を使用したアイテムを使用する場合は、ワールドクラフトを作る際に、ベータ機能を有効化する必要があります。
      こちらも同様に、後から無効化・有効化することができません。
  • イベント会場では使用できない
    • ベータ機能が有効なワールドはイベント会場として使用することができません。

ついに待望(一部のユーザーから)の外部通信機能がベータ機能としてやってきました!
https://note.com/cluster_official/n/n5fde9f98f369

外部通信機能 (callExternal)ってなぁに?

公式noteではこのように書かれています。

外部通信機能 (callExternal)

クリエイターで指定した URL に対して通信ができるようになる API です。
通信には任意のデータを含めて送信ができ、URL から返されたデータに応じてアイテムの振る舞いを変えることができます。

制約事項

  • 外部通信機能はベータ版として提供されます
  • サーバーと送受信できるデータのサイズは 1kB以下となります。
  • callExternal は 1アイテムにつき、5回/分 まで実行できます

「ベータ機能「外部通信機能」が使えるようになりました!、ベータ機能「メタデータ取得」の提供を再開しました 他【Cluster Creator Kit 2.7.0 リリースノート】」 より引用

なるほど!わからん!

つまりどういうこと?( ゚д゚)ポカーン

となった人が大多数かと思われます。

APIを理解しよう

この外部通信機能について理解するために、APIという概念を理解しておきましょう。

例えば、日本語の文章を英語に翻訳するとしましょう。
和英辞典を開いて、日本語の単語それぞれを英語に訳して、組み立てると、英語の文章になりました。

でも、もっと長い文章や難しい文章をこの方法であなたは翻訳できるでしょうか...?
あなたの知識(プログラムしている範囲内)で英語の文法が完全にカバーできているレベルではない場合、間違った結果を示す可能性もあります。

そこで、専門家の方をお呼びしましょう。
アメリカ人と日本人のハーフで、英語も日本語もしゃべることができるマイクです。
マイクに英文を言ったら、すぐに英文を返してくれました。

どうです?効率的でしょう?
このマイクの存在こそ、APIです。
APIは、決まった形式の呼びかけをして、APIは決まった形式で返事をします。
今回は、これはペンです, 英語という原文と翻訳先2つの情報をマイク(API)に渡して、マイク(API)はThis is a Penという訳文を返します。

実際のAPIの例

先ほどの例として、翻訳ツールのDeepLが提供するDeepL APIがあります。
https://www.deepl.com/ja/pro-api?cta=header-pro-api
DeepL APIには、以下の構文で翻訳を依頼します。

DeepL APIに翻訳を依頼する構文
{
  "text": [
    "これはペンです。"
  ],
  "target_lang": "EN"
}

その結果、DeepL APIは以下のように返事をします。

DeepL APIの返事
{
  "translations": [
    {
      "detected_source_language": "EN",
      "text": "This is a Pen."
    }
  ]
}

DeepL APIのように、世の中には公開されているAPIがたくさんあります。
そんなAPIの中でもインターネットを通じて情報をやりとりするAPIをWeb APIと呼びます。
公開されているAPIを活用することで、技術的に実装が難解になる機能の実装ハードルがとても低くなります。

公開APIのいろいろ

ウェブサイトを例に考える 「カタコトなAPIくん」

APIを使用してどのようにソフトなどが動いているかを見ていきましょう。

例えば、天気の情報を見ることができるウェブサイトがあるとしましょう。

ユーザーがウェブサイトにアクセスして、東京の天気を見ていますね。
このウェブサイトの裏側の動きを紐解いてみましょう。

  1. ユーザーがウェブサイトにアクセスする
    ユーザーがブラウザで、東京の天気のページを開いて読み込みが始まりました。
  2. ウェブサイトのサーバーが天気APIに天気の情報を問い合わせる
    ウェブサイトのサーバーがお天気APIに問い合わせて、東京の天気の情報を取得しています。
    ただ、APIはプログラムが理解しやすいような文章で返すので、カタコトです。人からすると読みにくいです。
  3. ウェブサイトのサーバーが天気APIからの返事を紐解いて、ユーザーにウェブサイトを届ける
    APIのカタコトな表現をウェブサーバーが紐解いて、ユーザーが理解しやすい形式に変換します。
    それをブラウザに送信して、ページが表示されました。

ウェブサイトなどのサービスの裏では、このようにAPIが絡んでいるものがあります。
が、APIはカタコトです。APIは、誰でも汎用的に使えるように設計されているため、APIの返事を我流にアレンジして、使用する必要があります。
今回の場合は、ウェブサーバーがAPIのカタコトを解釈して、ユーザーに分かりやすい画面を作ってくれました。

話を戻して、外部通信機能でなにができる?

ここまでAPIとはなにか?ということを話してきました。ここからはいよいよ外部通信機能の概要などに触れていきます。

なにができる?

まずはなにができるか、いくつか考えてみたのでメモ書きで書いておきます。

ワールド間、スペース間での通信

ワールドやスペースの垣根を越えて通信をすることができます。

  • 以前にリリースされたテキスト入力・出力機能を使用することで、スペース間で誰とでもチャットができるシステムを組んだりできると思います。
    https://note.com/cluster_official/n/nfb2ead17b6b4
    (clusterのチャット機能をスクリプトから扱う機能が欲しいですね...)

公開APIと連携したワールド

外部の公開APIと連携して、ワールドの機能を作ることができます。

  • 現実世界の天気をAPIで取得して、ワールドの天気を変える
  • ChatGPTと連携して、おしゃべりできるNPC
  • 今日のニュースやトレンドを取得して、ワールドで表示するサイネージ

ワールドの外にデータを出す、もらう

  • Discord API(Discord Webhook)を使う
  • スプレッドシートと連携して、ワールド内にあるギミックのメッセージを編集できるようにする
    • ゲームワールドのバランス調整などなど、パラメータの調整のために再アップロードが不要に!
    • イベント用ワールドで、イベント開催中のみドアを開けたいというときに、スプレッドシートから、開け閉めできるドアを作るなんてことも可能です。
  • ゲームワールドのハイスコアを保存する
    • ゲームワールドのグローバルのハイスコアを保存して、次にワールドに入った時もハイスコアを表示できます。
    • 今までのセーブ機能とは異なり、プレイヤーごとのハイスコアではなく、全ユーザーランキングでのハイスコアを保存することができます。

サーバーの用意が必須!公開APIをそのままワールドから触れない

この外部通信機能、言い換えると外部のWeb APIをワールドから使用できる機能というわけです。
ただ、そううまくいく話はありません。この外部通信機能は、ワールド制作者が用意したAPIサーバーとのみ通信が可能です。
この外部通信機能を使用するために、考慮しなければならない制限がいくつかあります。

  • ユーザーごとに1つの接続先(エンドポイント)しかAPIに使用できない
  • APIからの返信を使用するには、認証が必要になる
  • データの送受信は文字列でしか行うことができない
  • 送受信できるデータは1kb以下
  • APIを呼び出す回数の制限(5回/分)

ユーザーごとに1つの接続先(エンドポイント)しかAPIに使用できない・APIからの返信を使用するには、認証が必要になる

外部通信機能に使用するURLを事前にアカウントへ登録しておく必要があります。
登録は、Creator Kitを導入したUnity上から行うことができます。
注意点として、アカウントに一つしかURLを登録することができません。
そのため、複数のワールドで外部通信機能を使用したい場合は、APIにリクエストするときに、APIのどの機能を使用したいかという情報を含めてあげる必要があります。(後述)

URLを登録したときに、 verify用トークンが発行されます。アイテムにAPIから返された値をスクリプトから取得したい場合は、このトークンを含める必要があります。

アイテムにAPIから返すJSONの例
{
  verify: "verify用トークン",
  response: "文字列"
}

ここが、公開APIをワールドから触ることを難しくしている要因です。
ワールドでは、トークンが含まれている返答しか受信できないので、公開APIにアクセスできても、そのAPIの返答をスクリプトで受け取ることができません。

データの送受信は文字列でしか行うことができない・送受信できるデータは1kb以下

外部通信機能はAPIリクエスト、レスポンス共に、値は文字列でやりとりされます。
複数の値を扱いたい場合は、JSONを文字列化したうえで、外部通信機能に通すことが望ましいです。
javascriptには、JSON関数が用意されており、stringify parseを使用することで、jsonと文字列の相互変換が可能です。

JSON.stringify(json);  //jsonを文字列に変換する
JSON.parse(json);  //文字列をjsonに変換する

送受信できるデータは1kb以下という制限もあり、文字列化したjsonは、1kbに収めてjsonのフォーマットが崩れないようにする必要があります。
clusterスクリプトで外部通信機能を使用するために、1kb以下であるかをチェックする機能を設けることが推奨されます。
1kbの中にできるだけ多くの文字列を入れることができるように、jsonのオブジェクトの名前は
1文字にするのも効果的です。
このコードは、Discordにメッセージを送信するためのAPIに、ユーザー名とメッセージを送る外部通信機能の例です。

clusterスクリプトで、外部通信機能でJSONを送信するサンプル
/**
 * DiscordにWebHookでメッセージを送信する
 * @param text 送信するメッセージ
 * @param player メッセージを送信したプレイヤーのPlayerHandle
 */
function sendDiscordMsg(text, player) {
    let msg = {f:"DiscordWebHook", o:{u:playerHandle.userDisplayName, m:text}};
    
    //msgの合計が1kbを超えている場合
    if(JSON.stringify(msg).length > 1024) {
        //textから何文字削除すれば1kbを超えないかを算出して、textを削除する
        let textLength = text.length;
        let deleteLength = JSON.stringify(msg).length - 1024;
        trimText = text.substring(0, textLength - deleteLength);  //1kbに収まるようにテキストの最後を1kbに収まるまで削る
        msg = {f:"DiscordWebHook", o:{u:"userDisplayName", m:trimText}};
    }
    
    $.log("----\nDiscordにメッセージを送信しました。\n" + JSON.stringify(msg)+"\n----");
    ClusterScript.callExternal(DiscordWebHookURL, JSON.stringify(msg));
}

APIを呼び出す回数の制限(5回/分)

外部通信機能はアイテムごとに1分あたり5回しか実行できない制限があります。
そのため、その回数を超えて外部通信機能を使用しないようにコードを組む必要があります。
頻繁に外部通信機能を使用する必要がある場合は、外部通信機能を使用するためのアイテムをCreate Itemで生成して使用するなど、複数のアイテムで外部通信機能を使用することで回避が可能です。
特に、値の更新をチェックするために、頻繁に呼び出す場合は、

  1. 値の更新チェックのためのアイテムをCreate Itemして、外部通信機能を実行
  2. 5回外部通信機能を実行したら、次の外部通信のためのアイテムをCreate Itemして自身をDestroy Item
  3. 更新を検知したら、検知したことをSignalで通知して、メインとなるアイテムで外部通信を行う

このような実装も考えられます。

ほかにできないこと

基本的には文字でやりとりすることができ、前述の外部通信機能の制限をクリアできれば、ほとんどの情報をやり取りすることができます。
ただし、この機能を使って開発をするときは、以下のことを覚えておきましょう。

  • APIから情報を受け取っても、ワールドのギミックの先に実装できないものもある
    • APIから情報を受け取った後、ワールド内でギミックによる処理も作る必要があり、現時点のギミックでカバーできる範囲で作る必要があります。
  • 画像(映像)・音を扱うことはできない
    • スクリプト機能にはまだ画像や音を扱うための機能がないため、画像や音を扱うAPIを使用することができません。

サーバーをつくろう

さいごに、外部通信機能を触ってみたい!という人は、まずGASから触ることをおすすめします。
clusterのPMのSmithさんが入門向けに記事を書いてくれています!参考になる!
https://zenn.dev/smith/articles/cluster-easy-server
また今度詳細は触れるとして、今回は最低限のスクリプトを載せておきます。

POSTで文字列を受け取る最低限のサンプル
function doPost(e) {
  const request = JSON.parse(e.postData.getDataAsString()).request;
  //ここから処理を書く
}

さああなたもサーバーを作って、ワールドに新しい機能を作ってみてください!

Discussion