プラグインを書く
プラグインとはディスク上のフォルダーです。ふだんの IDE で編集し、ビルド工程なしでエディター内で 動き、できあがったら公開します。
はじめかた
Plugins パネルで Create を押します。Studio がフォルダーを尋ね、そこにテンプレートを 書き込みます。
my-plugin/
meta.json マニフェスト
main.html パネルのマークアップ
main.js コード本体。.ts・.tsx・.jsx も使えます
plugin.d.ts API 全体の生成済み型定義
tsconfig.json IDE がその型を解決できるように
.wasignore 公開時にアップロードしないもの
ファイルを編集して Reload from disk を押せば、変更がそのまま反映されます。バンドラーも インストール工程もありません。
.ts・.tsx・.jsx は読み込み時にコンパイルされますTypeScript と JSX をそのまま書けます。型は生成された plugin.d.ts から来るので、エディターが
API 全体を補完します。
マニフェスト
{
"id": "acme.shape-spawner",
"name": "Shape Spawner",
"version": "1.0.0",
"entry": "main.tsx",
"permissions": ["scene:read", "scene:write"],
"panels": [{ "id": "main", "title": "Shapes", "entry": "main.tsx" }],
"icon": "data:image/webp;base64,…"
}
アイコンはファイルパスではなく data URL です。そのため、ローカルのフォルダーでも公開済みの プラグインでも、現れる場所すべて(ツールバー、パネルのヘッダー、ライブラリのカード)で同じに 見えます。公開フォームが任意の画像から生成してくれます。
パネル
いちばん簡単なパネルは、HTML ファイルとスクリプトです。
<div id="root">Loading…</div>
<script type="module">
import { signal } from '@was/signals';
const clicks = signal(0);
init(async () => {
// ここでは editor・world・meta がそろっています
document.getElementById('root').textContent = `${meta.name} — ${world.getEntities().length} entities`;
});
</script>
HTML を使わず、entry に .tsx ファイルを指定してもかまいません。ランタイムがその
default export をマウントし、エディター自身の component ライブラリでパネルを組めます。
// main.tsx
import { useState } from 'react';
import { Button } from '@was/ui';
export default function Panel() {
const [clicks, setClicks] = useState(0);
return <Button onClick={() => setClicks(clicks + 1)}>{clicks}</Button>;
}
init() はワールドが本当に用意できるまで待ちますエディターへの接続が開き、かつシーンの同期コピーが用意できてから実行されるので、どちらも ポーリングする必要はありません。
グローバルは常に 4 つ使えます。editor(エディター API)、world(entity と component
としての、生きた同期済みシーンのコピー)、meta(マニフェスト)、そして init です。
読み込めるのは @was/ecs・@was/signals・@was/engine・@was/editor-api・@was/ui・
@was/icons・react・react-dom/client です。それ以外は、黙って何も解決しないのではなく、
はっきり失敗します。
@was/ui で組んだパネルは、あらかじめビルドされたスタイルシートを使うため、そこに含まれる
クラスしか存在しません。text-[13px] のような任意の値は含まれないので、スケール外のサイズには
インラインの style を使ってください。
シーンを扱う
world を通じて手で entity を組み立てることもできますが、シーンや space にはもっとよい方法が
あります。エディターに頼めば、エディター自身の既定値・採番・安全策がそのまま得られます。
await editor.scenes.list();
await editor.scenes.create({ name: 'Chapter 2', trigger: { type: 'image', imageId } });
await editor.scenes.setTrigger(sceneId, { type: 'surface', bindingType: 'wall' });
await editor.scenes.delete(sceneId);
await editor.spaces.create({ name: 'Lobby' });
trigger はアンカーのデータそのもので、エディターと同じスキーマで検証されます。だから新しい
トリガーの種類も、プラグインのプロトコルを変えずに動きます。画像トリガーは、物理サイズを
指定しなければ画像自体から割り出します。
自分が作ったものを後から見つける
シーンを生成するプラグインは、あとでそれを見つけられる必要があります。そのための tags です。
自動的にプラグインごとの名前空間に入るので、2 つのプラグインが互いの印を上書きすることはなく、
エディターは表示も変更もしません。
tags.set(entity, { kind: 'scene', card: '2' });
tags.find({ kind: 'scene' });
tags.remove(entity, 'card');
共有されるセッションの状態
状態のなかには、ドキュメントではなく「その集まり」に属するものがあります。走っているタイマー、 開催中の投票、挙がっている手。取り消しの対象にするのも、公開するのも、プロジェクトに保存するのも、 どれも違います。
プロジェクトルームは、いまそのプロジェクトにいる全員が見られる、小さな共有キー・バリュー ストアです。
const { now, entries } = await editor.room.get();
await editor.room.set('timer', { running: true, endsAt: now + 60_000 });
editor.on('room.changed', (state) => render(state.entries.timer));
await editor.room.delete('timer');
ページを再読み込みしても残り、12 時間で期限切れになり、8 KB のキーを最大 64 個まで持てます。
now がサーバーの時計であることには意味があります2 人のパソコンの時刻は数分ずれていることがあります。サーバーの時計に基づく絶対的な終了時刻を 保存し、カウントダウンは各クライアントに任せてください。「残り何秒」を保存してはいけません。 それでは、タイマーが走っている間じゅう、プロジェクトにいる全員のために毎秒ルームへ書き込むことに なります。
サーバーはどのキーを誰が書いたかも記録します。これがあるから、正直な投票が成立します。
vote/<userId> というキーが本当にそのユーザーによって書かれたときだけ、票として数えられます。
ビューポートのオーバーレイ
パネルは自分専用で、閉じることもできます。全員に見えていてほしいものには向きません。
"surface": "hud" を指定して宣言したパネルは、代わりにシーンの上に小さなオーバーレイとして
描かれます。
editor.hud.set({ visible: true, width: 240, height: 96 });
editor.panels.open('main'); // オーバーレイから自分のパネルを呼び出せます
最初は隠れていて、見せるものができたときに現れます。シーンの上にある見えない透明な枠は、 クリックを飲み込んでしまうからです。サイズの上限は 640×400 です。
全員に、常時出ているものなので、意図的に軽く作られています。表示に必要なものは、パネルが ルームに入れておきます。
公開する
公開フォームで指定できるのは、アイコン、名前、タグ 12 個まで、500 文字までの説明、 スクリーンショット 4 枚まで、そしてサブスクリプション限定のフラグです。
上限はファイル 1 つあたり 512 KB、64 ファイル、1 アカウントあたり 50 プラグインです。
もう一度公開すると同じエントリーが更新されます。公開済みのプラグインを編集用に ダウンロードすることもでき、選んだフォルダーにファイルが書き戻されます。
公開したプラグインのファイルはきちんと配信されるので、./icon.png は解決します。ローカルの開発
フォルダーがインライン化するのは .ts・.js・.json だけで、パスで参照した画像は公開するまで
表示されません。
権限
マニフェストには、プラグインが必要とするもの — scene:read・scene:write・resources:write・
spaces:write・collaboration:read・collaboration:write・editor:panels など — を並べます。
未知のものはサーバーが拒否します。
権限は、レビューする人が読むものであり、人がプラグインを判断する材料です。仕事が成り立つ いちばん狭い範囲を求めてください。
そして他人のプラグインを導入するときは、プロジェクト全体に手が届くものと考えてください。 信頼する理由のあるものだけを入れましょう。→ 信頼と検証
次へ: component の拡張