🦁

clusterのベータScriptで外部通信をするサンプル

2023/12/21に公開

clusterのベータScriptで外部通信

2023/12/19、ついにclusterでPOST通信が出来るようになりました。
そういうわけで早速使ってみました
分かる人向けの記事です

https://note.com/cluster_official/n/n5fde9f98f369

諸注意

・β機能です。正式版で仕様がかわる可能性があります
・サーバーサイドが分かる方向けです
・clusterのScriptもある程度わかる方向けです
・急いで作ったのでいろいろ汚いです
・非公式です

出来たモノ

https://twitter.com/sirojake/status/1737772384982389192

Unity側の構成

まずはβ機能をオンにします

次に、呼び出すAPIのURLを登録します。

登録するとトークンが表示されるので、メモっておきましょう

オブジェクトを追加していく

アイテム全体のオブジェクトを作ります。
AddComponentでScriptable Itemを作成して、Source Code Assetsにjavascriptをドラッグ&ドロップします。このjavascriptについては次で書きます

そうしたら今作ったスロットの下にCreate Emptyします。
TextViewという名前(何でも良いがscript側の名前と合わせる必要がある)にして、さらにAdd ComponentからText Viewを追加します

Script

Source Code AssetsにD&Dするjavascriptは下記になります。
verifyTokenのところは、さっき登録した時にメモったモノを入れてください

const textView = $.subNode("TextView"); // 子オブジェクトのTextViewを取得
const verifyToken = "xxxxxxxxxxxx";
const reloadTime = 20; //20秒に1回自動更新
let playerName;
let reloadTimeCount = 1;


$.onInteract(player => {
    player.requestTextInput("掲示板", "呟く");
    playerName = player.userDisplayName;
});


$.onTextInput((text, meta, status) => {
    switch(status) {
      case TextInputStatus.Success:
        param = {
            "verifyToken": verifyToken, 
            "name": playerName,
            "text": text
        };
        $.callExternal(JSON.stringify(param), "board");
        break;
      case TextInputStatus.Busy:
        // 5秒後にretryする
        $.state.should_retry = true;
        $.state.retry_timer = 5;
        break;
      case TextInputStatus.Refused:
        // 拒否された場合は諦める
        $.state.should_retry = false;
        break;
    }
  });


 $.onExternalCallEnd((response, meta, errorReason) => {
    let responseForJson = response.slice(1).slice(0, -1);
    boardContent = JSON.parse(responseForJson);
    
    renderText = "";
    boardContent.forEach(element => {
        renderText += element["n"]+" ("+ element["p"] +")"+"\n";
        renderText += element["t"]+"\n";
        renderText += "\n";
    }); 
    //表示テキストを更新
    textView.setText(renderText);
    reloadTimeCount = reloadTime;
  });



//自動更新
  $.onUpdate(deltaTime => {
		reloadTimeCount = reloadTimeCount - deltaTime;
		if (reloadTimeCount < 0.0) {
            param = {
              "verifyToken": verifyToken,
            };
            $.callExternal(JSON.stringify(param), "board");
            reloadTimeCount = reloadTime;
        }
   });

サーバー側

次にサーバー側なんですが、今回はlaravelで書きました。
ちょっと説明がむずいのでcallbackの値だけ示しておきます。
(ソースコード自体はこちらです https://github.com/ginjake/clusterPost)

{
  "verify": "xxxxxxxxx",
  "response": "'[{\"n\":\"\\u9280\\u9bad\",\"t\":\"\\u3042\\u3042\\u3042\\u3042\",\"p\":\"cluster\"},{\"n\":\"\\u9280\\u9bad\",\"t\":\"\\u3042\\u3042\\u3042\\u3042\",\"p\":\"cluster\"},{\"n\":\"ginjake\",\"t\":\"aaaaa\",\"p\":\"resonite\"},{\"n\":\"\\u9280\\u9bad\",\"t\":\"\\u3066\\uff53\\uff54\\u3066\\uff53\\uff54\",\"p\":\"cluster\"},{\"n\":\"\\u9280\\u9bad\",\"t\":\"\\u3066\\uff53\\uff54\",\"p\":\"cluster\"}]'"
}

ここで大事なのが2点。
・verifyをcallbackのレスポンスに含める必要がある。
ここのverifyはAPIのURLを登録した時にメモったモノです。
ここが違うと失敗します

・responseの中に入れるのは文字列だけ
jsonを返そうとすると失敗します。そのためjsonの前後に`をつけて文字列に、script側で取り除く処理をしています

以上です

その他

・可能なのはPOST通信のみ。
さらに、通信できるURLは1アカウントにより1つ。
(新規にURLを登録しなおしても、前のは生きてるという情報もある)

今回は掲示板の更新と投稿で2つのAPIが欲しかったが、
分けることが出来ないのでrequestのパラメータを見て処理を分けてる。
多分$.callExternalの第二引数metaで、そのへんの振り分けをするのが良さそう

・送受信できる値は文字列のみ。jsonをそのまま送れない
・サーバーと送受信できるデータのサイズは 1kB以下となります。(公式より)
・callExternal は 1アイテムにつき、5回/分 まで実行できます(公式より)

参考になった資料

テキスト入力
https://docs.cluster.mu/script/interfaces/PlayerHandle.html#requestTextInput

テキスト出力
https://docs.cluster.mu/script/interfaces/SubNode.html#setText

TextViewコンポーネント
https://docs.cluster.mu/creatorkit/world-components/text-view/

callExternal
https://docs.cluster.mu/script/interfaces/ClusterScript.html#callExternal

onExternalCallEnd
https://docs.cluster.mu/script/interfaces/ClusterScript.html#onExternalCallEnd

SubNode
https://creator.cluster.mu/2022/12/20/scriptable-enabled/

追記

実際には排他処理が必要です
思いつく限りでは
・APIのコール上限数に達した場合の処理
・送信コメントに文字数上限をつける
・受信時に1kbを超えた場合の処理

また、onInteractのPlayer名を取得してる部分でも
複数ユーザーが同時にInteractした場合を考慮してないので
そのあたりの検証や考慮が必要だと思います

Discussion