ランタイムコード
公開したプロジェクトで動くコードをプラグインに持たせます。オブジェクトを動かすシステム、イベントのトリガーとステップ、プロジェクト全体の設定を扱います。
パネルとカスタムコンポーネントは、エディタの中で動きます。ランタイムコードは、プロジェクトが再生されるあらゆる場所で動きます。Web とアプリで公開したプロジェクト、エディタのプレビュー、プレイテストです。
ランタイムコードを追加する
マニフェストの runtime に、プラグインのフォルダー内の .ts または .js ファイルを指定します。
{
"id": "acme.spin",
"name": "Spin",
"version": "1.0.0",
"entry": "main.tsx",
"permissions": [],
"panels": [],
"components": [{ "id": "spin", "title": "Spin", "entry": "spin.tsx" }],
"runtime": "runtime/index.ts"
}
このファイルは、プロジェクトの開始時に、プロジェクトのスクリプトと並んで 1 回だけ実行されます。ここでシステムを登録します。システムは毎フレーム動きます。
// runtime/index.ts
import { Collection, System } from '@was/ecs';
import { TransformComponent, frameTime, pluginComponent } from '@was/engine';
class AcmeSpinSystem extends System.from('AcmeSpinSystem') {
execute() {
const { dt } = frameTime();
const spin = pluginComponent('acme.spin/spin', this.space);
for (const e of spin.entities()) {
const t = e.getComponent(TransformComponent)!;
const r = t.$data.rotation;
t.update({ rotation: { ...r, y: r.y + (spin.props(e)!.speed as number) * dt } });
}
}
}
Collection.registerSystem(AcmeSpinSystem);
このシステムは、プラグインの Spin コンポーネントを持つすべてのオブジェクトを、インスペクターで設定した速さで回転させます。
システムの名前は、プロジェクト内のすべてのもので共有されます。名前がすでに使われていると、システムは起動せず、エラーにその名前が表示されます。AcmeSpinSystem のように、どの名前もプラグインの名前で始めてください。
ランタイムコードで使えるもの
ランタイムコードは、スクリプトと同じものをインポートできます。@was/ecs、@was/engine、@was/utils、@was/svdt です。配布されるのは、エントリーファイルと、そこから ./ でインポートしたファイルだけで、合計 1 MB までです。
ページ、DOM、three.js、React はありません。ランタイムコードはコンポーネントを読み書きし、その結果をエンジンが描画します。
| 関数 | 得られるもの |
|---|---|
frameTime() | { dt, t }:前のフレームからの時間と、開始からの時間(秒) |
pluginComponent(key, space) | オブジェクトについたカスタムコンポーネント。読み書きできます |
pluginEvents(id, space) | トリガーの発生と、ステップへの応答 |
pluginSettings(id) | プロジェクトで選ばれた設定の値 |
pluginContext(id, space) | スクリプトの ctx:入力、物理、レイ、サウンド、グローバル、マルチプレイヤー |
key は <plugin id>/<component id> の形で、acme.spin/spin のように書きます。id は、マニフェストにあるプラグインの id です。
自分のコンポーネント
pluginComponent(key, space) では、次のメソッドを使えます。
| メソッド | 動作 |
|---|---|
entities() | コンポーネントを持つオブジェクト |
has(e) | オブジェクトでコンポーネントがオンになっているかどうか |
props(e) | インスペクターの値。指定のないものはデフォルト値になります |
state(e) | コンポーネント自身のステート |
setState(e, patch) | ステートを変更します |
setProps(e, patch) | インスペクターの値を変更します |
setEnabled(e, on) | コンポーネントをオンまたはオフにします |
add(e, props) | オブジェクトにコンポーネントを追加します |
remove(e) | コンポーネントを削除します |
スクリプトでは、同じものを ctx.plugin(key) として使えます。
スクリプトのコンテキスト
pluginContext(id, space) は、スクリプトが受け取るのと同じ ctx を返します。入力、物理、レイキャスト、オーディオ、グローバル、マルチプレイヤーに使います。プロジェクトのスクリプトが起動するまでは null で、ワールドが変わると一緒に変わることがあります。そのため、必要になったときに取得してください。ctx.on と ctx.tick は呼び出すたびに新しいハンドラーを追加するので、購読はコンテキストごとに 1 回にしてください。
トリガーとステップ
ランタイムコードを持つプラグインは、イベントパネルに独自のトリガーとステップを追加できます。利用者は、組み込みのものと同じように、コードなしでそれらを使えます。トリガーとステップは、プラグインのあるプロジェクトで、プラグインの名前の下に表示されます。
マニフェストで宣言します。
"triggers": [
{ "id": "reached", "title": "Reached a point",
"params": { "point": { "type": "number", "default": -1, "any": -1, "label": "Point" } } }
],
"steps": [
{ "id": "pause", "title": "Pause",
"params": { "seconds": { "type": "number", "default": 0, "min": 0 } } }
]
- ID はケバブケースで書きます。正式な名前は
<plugin id>:<id>で、acme.patrol:reachedのようになります。 paramsには、definePropsと同じフィールドの型を使います。number、string、boolean、color、select、entity、resourceです。パネルには、それに合わせたフォームが表示されます。- トリガーの params は、フィルターとして働きます。ポイント 2 に設定したイベントは、トリガーが
point: 2で発生したときだけ実行されます。param を空のままにするか、anyの値に設定すると、トリガーが発生するたびにイベントが実行されます。
ランタイムコードから、トリガーを発生させ、ステップに応答します。
import { pluginEvents } from '@was/engine';
class AcmePatrolSystem extends System.from('AcmePatrolSystem') {
events = pluginEvents('acme.patrol', this.space);
off = this.events.onStep<{ seconds: number }>('pause', (targets, params) => {
// `targets` のオブジェクトを params.seconds 秒間止める
});
execute() {
// 警備員がポイントに着いたとき:
// this.events.fire('reached', guard, { point: 2 });
}
dispose() {
this.off();
}
}
fire(id, object, payload)は、そのオブジェクトと親オブジェクトで、このトリガーを使うイベントを実行します。オブジェクトを指定しない場合は、すべての場所で発生します。スクリプトではctx.on('acme.patrol:reached', payload => …)で受け取れます。onStep(id, handler)は、イベントがあなたのステップを実行したときに動きます。スクリプトからも、ctx.step('acme.patrol:pause', { seconds: 2 })で実行できます。購読は 1 回だけにして、返された関数をdispose()で呼び出してください。- プラグインがプロジェクトから外されると、そのトリガーは発生しなくなり、ステップはスキップされます。
プロジェクト設定
テンポ、難易度、色など、プロジェクト全体に属する値もあります。それらは settings の下で宣言します。フィールドは 24 個までで、型は number、string、boolean、color、select です。
"settings": {
"tempo": { "type": "number", "default": 1, "min": 0, "label": "Tempo" }
}
利用者は、プロジェクト設定の、プラグインの名前がついたセクションでこれらを設定します。
- ランタイムコードは、
pluginSettings<{ tempo: number }>('acme.patrol')で値を読みます。すべてのフィールドが得られます。プロジェクトで設定した値か、デフォルト値です。値はエントリーが実行される前に準備され、プロジェクトの再生中は変わりません。 - 公開したプロジェクトでは、公開したときの値が使われます。
- パネルでは、
editor.pluginSettings.get()とeditor.pluginSettings.set({ tempo: 2 })で値を読み書きします。これにはsettings:write権限が必要です。
コードがプロジェクトに届くまで
ランタイムコードが動くのは、プラグインが追加されているプロジェクトだけです。
- プラグインを追加すると、バージョンが固定されます。 プロジェクトは、その時点のコードを保持します。あなたが新しいバージョンを公開したあとも、プレビュー、プレイテスト、公開ではそのコードが動きます。
- 更新するかどうかは選べます。 新しいコードがあると、Plugins パネルのプラグインに update available と表示されます。Update in project を押すと、プロジェクトが新しいコードに切り替わります。
- 自分のプロジェクトには、保存がすぐ反映されます。 自分のプラグインをプロジェクトに追加した場合は、保存するたびにそのバージョンがプロジェクトで動きます。
- 公開したプロジェクトは、そのコードを保ちます。 公開したプロジェクトでは、もう一度公開するまで、公開したときのコードが動きます。
トリガー、ステップ、設定のタイトル、説明、ラベルは、${…} と書けばロケールファイルから読み込めます。
購読者限定のプラグイン
プラグインのランタイムコードは、公開したプロジェクトを開いた全員に届き、誰でもブラウザで読めます。これは Subscribers only のプラグインでも同じです。サブスクリプションで決まるのは、プラグインをプロジェクトに追加し、更新し、それを使って公開できる人です。コードが隠れるわけではありません。
サブスクリプションが終わると、そのプラグインを使っているプロジェクトはもう一度公開できなくなります。サブスクリプションを更新するか、プラグインを外してください。すでに公開されているプロジェクトは、そのまま動き続けます。
開発者向け
- ランタイムコードは、プロジェクトのスクリプトワーカーで、プロジェクトのスクリプトと一緒に動きます。
- プロジェクトが保持するバージョンは、ランタイムのファイル、トリガー、ステップ、設定のフィンガープリントです。フィンガープリントが変わると、update available が表示されます。
次へ: キーボードショートカット — 編集を速くするキー操作。