💥

ClusterScriptの読み方

2024/12/01に公開

この記事は Cluster Script Advent Calendar 2024 の1日目の記事です。

はじめに

こんにちは Smith です、クラスター株式会社のプラットフォーム事業部でプロダクトマネージャーをやっています。
クラスター株式会社では、バーチャル空間向けのコンテンツを作ったり誰かが作った様々なコンテンツを楽しめる cluster というサービスを提供しています。

cluster とスクリプト

cluster ではコンテンツをつくるためのツールとして Cluster Creator Kit (CCK) を提供しています。
CCK は Unity プロジェクトにインストールするパッケージとして提供されており、Unity シーンに CCK のコンポーネントを組み合わせることで誰もがバーチャル空間でのインタラクティブなコンテンツを作ることができます。

CCK では JavaScript をベースにした ClusterScript というスクリプトを扱うことができます。
スクリプトを扱うことでより自由度の高いコンテンツをつくることができます。

この記事について

ゼロからスクリプトを書いていくのは大変な作業です、誰もが最初はどこかの誰かが書いたスクリプトを参考にしたりするでしょう。
この記事ではシェアされたスクリプトを読む時に押さえておきたいポイントを解説していきます。
スクリプトを書いて共有する側の方も、観点を参考にしてみてください。

ClusterScript の読み方

使い方が書かれたドキュメントを読む

シェアされているスクリプトやアイテムでは、ほとんどの場合は使い方を示すドキュメントが付随していると思います。
スクリプトを利用するための準備や環境・条件などをチェックしましょう。
例えば下記のような環境が明記されていない場合、そのスクリプトが作られた時期が古ければ動作しない場合もあると思います。

  • ベータ版環境専用かどこでも動作するか
  • ワールドクラフト用か CCK ワールド用か
  • このスクリプト・アイテムを作成したときの cluster や CCK のバージョン

また、スクリプトをシェアしている人によって想定している前提知識が異なります。
使い方のドキュメントに Unity インストールから記述されている場合もあれば、スクリプトの内部実装について詳しく解説されている場合もあるでしょう。
ClusterScript 固有ではない知識は前提知識とされる場合が多いため、以下のようなドキュメントも併せてすぐに見られるようにしておくとよいでしょう。

最後に、手にしたスクリプトのライセンスを確認しましょう。
ライセンスといっても、改変や二次配布、商用利用が OK かなどの肝要な部分がわかれば OK です。
オープンソースライセンスやクリエイティブ・コモンズなど、定型的なライセンスが適用されている場合は公式の定義を参照しましょう。

ドキュメントを一通り読んだら、実際に自分のアイテムやワールドに組み込んで cluster 上で動作を確認しましょう。
正しい動作を確認しておくことで、スクリプトを読むときも理解しやすくなります。

スクリプトの構造を捉える

実際にスクリプトを読み始めるときは、全体の構造を把握しましょう。

ClusterScript は、$.onXXX のように XXX をした時に呼ばれる処理を登録する(コールバック)事が多いため、ある程度は定型的な構造になることが多いと思います。
そのため、まずは $.onXXX としてどのようなものが書かれているか把握しておきましょう。

例えば掴めるアイテムだったら $.onGrab が書かれています。
更に、掴んで使える場合は $.onUse も書かれているでしょう。

$.onGrab((isGrab, isLeftHande, player) => {
    なんか書いてあるけど後で読む
});

$.onUse((isDown, player) => {
    なんか書いてあるけど後で読む
});

乗り物や座れるアイテムであれば $.onRide が使われており、更に操縦できる場合は $.onSteer, $.onSteerAdditionalAxis などが使われているでしょう。

$.onRide((isGetOn, player) => {
    なんか書いてあるけど後で読む
});

$.onSteer((input, player) => {
    なんか書いてあるけど後で読む
});

$.onSteerAdditionalAxis((input, player) => {
    なんか書いてあるけど後で読む
});

ここで重要なのは、スクリプトを読み始める場所を理解することです。
スクリプトのファイルの 1行目から読むよりも、掴んだときやインタラクトした時などの処理の流れを追いながら読んだほうが理解しやすいと思います。
例えば、インタラクトしたら何かが起こるアイテムは、 $.onInteract の中身から読み進めるとわかりやすいでしょう。
インタラクトしたら何かが起こるという仕様自体は、あらかじめ使い方のドキュメントを読んだり、実際に cluster の上で動かしたりして理解しているはずです。

プレイヤーの動作がきっかけではない $.onXXX

上記で紹介した $.onGrab などは、ワールド内でのプレイヤーの直接の操作をきっかけとするものです。
プレイヤーの操作がきっかけではなくても頻出する $.onXXX はいくつかあるのであらかじめ把握しておいたほうが良いでしょう。

$.onStart

アイテムが新しく生成されたときやスペースが立ち上がった時に処理する内容を書く場所です。

$.onUpdate

定期的に実行される処理を書く場所です。
1秒間におよそ 20~30回の頻度で実行されます。

$.onReceive

他のアイテムからメッセージを受け取った時に実行される処理を書く場所です。
つまり、$.onReceive が書いてあるだけで、他のアイテムと連動していることがわかります。

その他にも $.onXXX は存在しますが、ここでは割愛します。

仕様を把握してスクリプトを読む

構造を把握したら、スクリプトの具体的な処理を読んでみましょう。

スクリプトに書かれている内容を正確に理解する必要があるときは、スクリプトを改変しようと思ったり1から書いてみようとするときです。

最初はよくわからない部分は読み飛ばして大丈夫です。

英文読解とおなじで、1単語ずつちゃんと理解するよりも 60% ほどの理解で文章全体の流れやコンテクストを理解する方を優先したほうが後々読み進めやすくなります。

実際に私が公開している Unity プロジェクトで用いているスクリプトの一部を読んでみましょう。
下記の github プロジェクトで管理されている Unity プロジェクトから引用しています。

https://github.com/dolow/ItemVisibilityFilterSample

スクリプトを読む前に、ある程度ワールドやスクリプトの仕様を説明しておきます。

  • このワールドは人狼ゲームをするためのワールドです
  • 人狼ゲームを始めるためには最低でも 4人のプレイヤーが必要です
  • あるアイテムにインタラクトすると、いまスペース内にいるプレイヤーをランダムに人狼と村人に振り分けます
  • 人狼はプレイヤーの中から2人選出されます
  • 人狼は誰が人狼の仲間かが分かるメッセージが壁に表示されます
  • 村人は自分が村人であることがわかるメッセージが壁に表示されます
  • メッセージは同じ壁に表示されますが、人狼と村人の見えるメッセージは異なります
  • このスクリプトは下記の処理を行っています
    • プレイヤーを人狼と村人を振り分ける
    • 誰がどのメッセージを見ることができるかを壁アイテムに伝える

このことを踏まえてスクリプトを読んでみましょう。
スクリプトで出てきた単語から仕様を連想するゲームのようなものです。

$.onInteract((playerHandle) => {
    let villagers = $.getPlayersNear($.getPosition(), Infinity);

    if (villagers.length < 4) {
        return;
    }

    let wolves = [];

    for (let wolfNeedCount = 2; wolfNeedCount > 0; wolfNeedCount--) {
        let index = Math.floor(Math.random() * villagers.length);
        wolves.push(villagers[index]);
        villagers.splice(index, 1);
    }

    const wallWolf = $.worldItemReference("wall_wolf");
    const wallVillager = $.worldItemReference("wall_villager");

    wallWolf.send("change_visibility", wolves);
    wallVillager.send("change_visibility", villagers);
});

事前にワールドやスクリプトの仕様のインプットがあれば、なんとなく扱っている仕様が分かる箇所がいくつか出てくるかと思います。

例えば下記は villagers に関係のありそうな length と、4という数字を不等号 < で比較しているということから、人狼ゲームを始めるためには最低でも 4人のプレイヤーが必要です という仕様を表現している部分ではないかと連想できます。

if (villagers.length < 4) {
  return;
}

下記も wolfNeedCount という名前や 2 という数字、 random という単語が出てきているので、いまスペース内にいるプレイヤーをランダムに人狼と村人に振り分けます人狼はプレイヤーの中から2人選出されます という仕様に関係がありそうです。

for (let wolfNeedCount = 2; wolfNeedCount > 0; wolfNeedCount--) {
    let index = Math.floor(Math.random() * villagers.length);
    wolves.push(villagers[index]);
    villagers.splice(index, 1);
}

このようにどこで何をやっているかをなんとなく整理することで、ちゃんと読んだほうが良い箇所とそれほど重要ではない箇所の区別ができるようになります。

コメント

スクリプトには書いた人がコメントで補足を入れてくれている場合があります。
コメントはスクリプトを書いた本人のためのメモであると同時に、そのスクリプトを他の人が読んだときのための情報でもあります。

一見して複雑な箇所も、一言コメントを書いておくだけでその処理が重要かそうでないかの見分けがつくようになります。

行内において // 以降にかかれたテキストか、 /* で始まり */ で終わる箇所がコメントです。

// これはコメント

/* これもコメント */
"ここはコメントではない"
/*
複数行に渡るコメント
*/

もしも手元にスクリプトが編集できる環境があるのであれば、自分で読み解いた情報をコメントとして追記してもよいでしょう。

// 人狼を2人選出するまで繰り返す
for (let wolfNeedCount = 2; wolfNeedCount > 0; wolfNeedCount--) {
    // 村人リストの中の何番目の村人を人狼にするかを決める
    let index = Math.floor(Math.random() * villagers.length);
    wolves.push(villagers[index]);
    // 人狼になった番号の村人は村人リストから取り除く
    villagers.splice(index, 1);
}

カスタマイズできる要素を考える

公開されているスクリプトの中には「ここをこう編集するとカスタマイズできます」のように明確にカスタマイズの余地があるものもあります。

そうでないスクリプトでも、仕様を理解してスクリプトを読んでいくとカスタマイズできそうな箇所が見つかる場合があります。
特に数値やテキストが関係する仕様は比較的かんたんにカスタマイズできるでしょう。

先程の人狼ゲームを例に上げると、これらの仕様の数字の部分はカスタマイズできそうです。

  • 人狼ゲームを始めるためには最低でも 4人のプレイヤーが必要です
  • 人狼はプレイヤーの中から2人選出されます

これまで見てきたように、数値の部分はスクリプトの中でも比較的見つけやすいかと思います。

if (villagers.length < 4) {
for (let wolfNeedCount = 2; wolfNeedCount > 0; wolfNeedCount--) {

4人以上ならプレイできる、という部分を試しに1人にしてみることで、1人での動作確認でもスクリプトの動きが変わるようになると思います。

この時、プレイ人数が1人にも関わらず人狼を2人以上選出しようとするとエラーになるので、人狼の数も合わせて 0 もしくは 1 に変えておきましょう。
カスタマイズするときはプレイ人数と人狼の人数のようなロジック的な不整合に気をつける必要があります。

解説してみる

ClusterScript くらいの規模のスクリプトの場合、スクリプトを1から書くよりもすでに書かれているスクリプトの概要を読み解く方が必要な知識が少なくて済む場合が多いです。
自分でスクリプトにコメントを入れたり、カスタマイズのしかたを紹介してみるのもスクリプト理解の手助けとなります。

解説してみようと思った場合は下記のような記事形式の媒体がよく用いられます、自分の活動に合った媒体を使ってみましょう。

note

技術的なトピックに限らず広く用いられる媒体です。
多くのユーザーに馴染みがあるかもしれません。

Qiita

技術者がよく用いる媒体で、スクリプトの解説に適しています。
CCK やスクリプトの解説記事を公開する場所としても申し分ないかと思います。

zenn

Qiita と同じく技術者がよく用いる媒体です。
記事形式の他に、本としても文章をまとめたり公開できたりします。

まとめ

  • 最初に使い方のドキュメントをちゃんと読もう
  • スクリプトを読む前に動かせるものは動かしてみよう
  • スクリプトは仕様からの連想ゲームで読んでいこう
  • 自分でコメントを書きこんだり、解説記事を書いてみたりしよう

コミュニティ

本稿では、第三者からスクリプトがシェアされている前提でスクリプトの読み方について解説していきました。

「心血注いで書いたスクリプトを無償で共有するなんて!」という考えはもっともです。
企業でも、自社が投資した技術を外部に公開するほうが稀です。

しかし我々は企業とは異なり cluster のものづくりに関するコミュニティの一員です。
cluster のコミュニティでは以下のような経験があるかと思います。

  • 自分の作ったものについての感想をもらう
  • 質問に答えてもらう
  • スクリプトや記事などをシェアしてもらう

コミュニティは互助関係です。
あなたがコミュニティからなにかしらのサポートを得られる代わりに、別のなにかの形でコミュニティに還元するというサイクルでコミュニティが成り立っています。

コミュニティにシェアされているスクリプトや知見はコミュニティ全体の資産ですが、シェアして終わりではなくて更に発展できる余地があります。
つまりコミュニティは、今あるものよりも更に良いものが生まれることを加速させる場でもあるのです。

コミュニティから何かしらのベネフィットを受けられる、コミュニティの一員としてコミュニティに還元していく。

これが cluster のクリエイターコミュニティのあり方です。

むすびに

正直、スクリプトはむずいし、スクリプトを書かないで済むようにしていきたいと思っていますが、 そのためにはスクリプトというバックボーンが必要です。

スクリプトを書かないでも cluster で色々なものが簡単につくれる未来を一緒につくっていきましょう。

それじゃ、アリーヴェデルチ!

Discussion