はじめての mod
mod は、ワールドに置ける機能の部品です。TypeScript で書き、利用者のブラウザの Worker(サンドボックス)の中で動きます。 ここでは「押すと全員で数が増えるボタン」を作って、手元で動かすところまでやります。
用意するもの
Section titled “用意するもの”- Node.js(
@ubichill/cliが動く版)と npm - エディタ。TypeScript の補完が効くもの
Docker・データベース・Go のツールチェーンは要りません。
1. プロジェクトを作る
Section titled “1. プロジェクトを作る”mkdir like-mod && cd like-modnpm init -ynpm install ubichillnpm install -D @ubichill/cli typescript| パッケージ | 役割 |
|---|---|
ubichill |
SDK。mod が import するのはこれだけ。依存ゼロ |
@ubichill/cli |
ubichill コマンド。開発サーバー・ビルド・固定・検証・署名・公開。Go のインスタンスサーバーも一緒に入る |
package.json を次のようにします。name が mod の ID になります(@ubichill/mod- の接頭辞は外されます。別の ID にしたいときは "ubichill": { "id": "like" } を足します)。
{ "name": "like", "version": "0.1.0", "private": true, "dependencies": { "ubichill": "^4" }, "devDependencies": { "@ubichill/cli": "^1", "typescript": "^5" }, "scripts": { "dev": "ubichill dev", "typecheck": "tsc --noEmit", "build": "npm run typecheck && ubichill build" }}2. 設定ファイル
Section titled “2. 設定ファイル”tsconfig.json:
{ "compilerOptions": { "target": "es2022", "lib": ["ES2022", "WebWorker"], "module": "esnext", "moduleResolution": "bundler", "strict": true, "noEmit": true, "jsx": "react-jsx", "jsxImportSource": "ubichill" }, "include": ["src"]}src/env.d.ts(Ubi はグローバルに注入されるので、型だけ宣言します):
declare const Ubi: import('ubichill').Ubi;3. Component を書く
Section titled “3. Component を書く”src/button.worker.tsx:
import type { ComponentConfig } from 'ubichill';
// このファイルが 1 つの Component になる。ID は "like:button"export const config: ComponentConfig = { defaultTransform: { x: 0, y: 0, w: 140, h: 44 }, dataFields: { label: { type: 'string', default: 'いいね', label: 'ボタンの文字' }, },};
// 状態。sync を付けた値は、この Entity を見ている全員で共有され、部屋が続く間残るconst like = Ubi.state.define({ count: Ubi.state.sync(0), label: Ubi.state.sync('いいね'),});
// export default が UI。読んだ Ubi.state の値が変わると自動で描き直されるexport default function LikeButton() { return ( <button onClick={() => like.local.count++}> {like.local.label} {like.local.count} </button> );}決まりごと:
- ファイル名は
*.worker.tsか*.worker.tsx。export const configを持つファイルだけが Component としてビルドされます。 - Component の型は
<mod の ID>:<ファイル名から .worker を外したもの>。ここではlike:button。 dataFieldsに書いたものは、ワールドエディタのインスペクターで作者が編集できるようになります。- UI は
export defaultの関数が返します。onClickなどのハンドラは Worker の中で動きます。
4. 手元で動かす
Section titled “4. 手元で動かす”npm run dev ubichill dev v1.1.0 ready in 447 ms
➜ Local: http://localhost:3333/http://localhost:3333/ を開くと、like:button を 1 つ置いた部屋が自動で作られ、ボタンが出ます。
別のタブでも開いて押してみてください。数が同期します。
- ファイルを保存すると、その mod だけビルドし直して画面が更新されます。共有している状態(数)は残ります。
- ビルドに失敗したら前の版が動き続け、失敗の内容が画面とターミナルに出ます。
- スマートフォンから試すには
ubichill dev --hostで LAN に公開します。
自分のワールドに置いて試すなら、world.yaml を書いて ubichill dev world.yaml とします(ワールド定義フォーマット)。
手元でビルドした mod は、dependencies に source.url が書いてあっても手元のものが使われます。
5. 権限はどう決まるか
Section titled “5. 権限はどう決まるか”このボタンは Ubi.state.sync と export default を使っているので、ビルド時に scene:read・scene:update・ui:render が自動で付きます。
開発者が宣言する必要はありません。
| ティア | 既定の動き | 例 |
|---|---|---|
| safe | 確認なしで許可 | UI の描画、シーンの読み取り、イベントの送信 |
| sensitive | 既定で許可(利用者が「要承認」に変えられる) | シーンの変更、ブロードキャスト、キャンバス描画、メディアの制御 |
| dangerous | 利用者の明示的な承認が要る | 外部通信(Ubi.fetch)、外部サービスへの身元証明 |
外部と通信するなら Ubi.fetch を使います。接続先のドメインごとに利用者が許可します。素の fetch は Worker の中では使えません。
一覧は 権限リファレンス にあります。
6. ビルドと検証
Section titled “6. ビルドと検証”npm run build # tsc --noEmit → ubichill buildnpx ubichill verify --dist-dir=distdist/v0.1.0/ に manifest.json(実行用)と lock.json(バイト列のハッシュと権限の上限)ができます。
verify は、配るバイト列と lock.json が一致するかを独立に計算し直して確かめます。
7. 公開する
Section titled “7. 公開する”mod の配布物には作者アカウントの署名が必須です。署名のない mod を本番の Host は実行しません。
npx ubichill login # 1 回だけ。ブラウザで承認するnpx ubichill publish dist # dist/v0.1.0/lock.sig.json を書き出すnpx ubichill verify --dist-dir=dist --require-signaturesdist の中身を GitHub Pages などに置けば完成です。ワールドの作者は dependencies[].source.url にその URL を書いて使います。
--public-url=https://あなた.github.io/like を付けると、あなたのユーザーページの「公開している mod」に出ます。
署名・公開環境・CI からの公開は 署名して公開する にまとめてあります。
- SDK API —
Ubi.state・Ubi.event・Ubi.entity・Ubi.uiなどの全体 - SDK README — ネームスペースの一覧と JSX の使い方
- CLI —
ubichill dev / build / install / verify / publishの詳細 - mod パッケージ — manifest・lock・配布の形式
- 実行環境 — WASM・同梱アセット・バイナリ通信