メインコンテンツまでスキップ

はじめての script

script は、オブジェクトに付ける TypeScript のファイルです。プレイヤーの中で、それを使う オブジェクトごとに 1 つずつ動きます。

これが完結した例です。オブジェクトをタップすると回りはじめます。

Scripts/spinner.ts
init((ctx) => {
const props = defineProps({
speed: { type: 'number', default: 1, min: 0, label: 'Speed' },
});

let spinning = false;

ctx.on('on-click', () => {
spinning = !spinning;
});

ctx.tick((dt) => {
if (!spinning) return;

const t = ctx.entity.getComponent(TransformComponent);
t?.update({
rotation: { ...t.$data.rotation, y: t.$data.rotation.y + props.speed * dt },
});
});

return () => {
// 任意: このオブジェクトが消えるときの後片付け
};
});

ここで起きていることは 4 つです。

init動いているシーンへの取っ手 ctx を受け取ります。すべてはここから始まります
definePropsエディターのインスペクターに出す設定を宣言します。値はオブジェクトごとに入ります
ctx.onトリガーを購読します。イベントや patch と同じトリガーです
ctx.tick毎フレーム実行されます。dt は秒です

initdefineProps は常に使えます。import は不要です。

オブジェクトの読み書き

const transform = ctx.entity.getComponent(TransformComponent);

transform.position; // 値を 1 つ読む
transform.$data; // 全体を素のオブジェクトとして読む
transform.update({}); // 書き込む

落とし穴が 2 つあり、これは全員が引っかかります。

入れ子の値を書くと、丸ごと置き換わります

update({ position: { y: 2 } })xz も 0 にします。y だけ入った新しい position を まるごと渡したことになるからです。

残したいものは展開してください。

transform.update({ position: { ...transform.$data.position, y: 2 } });
マテリアルはスロットのリストです

material.update({ color }) は何も起きません。color は最上位ではなくスロットの中にあるからです。 スロットを書いてください。

material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});

それと、$data に直接代入してはいけません。うまくいったように見えて、変更は捨てられます。

インスペクターの設定

defineProps こそが、script を使い回す価値のあるものにします。同じ script を 10 個の オブジェクトに付け、それぞれ別の設定にできます。コピペは不要です。

const props = defineProps({
speed: { type: 'number', default: 1, min: 0, softMax: 10, suffix: 'm/s' },
target: { type: 'entity', label: 'Look at' },
sound: { type: 'resource', resource: 'audio' },
mode: { type: 'select', options: ['chase', 'patrol'], default: 'patrol' },
});
エディターの表示script が受け取るもの
number入力欄またはスライダー数値
stringテキスト入力欄文字列
booleanスイッチ真偽値
colorカラーピッカー色の文字列
selectドロップダウン指定した選択肢のどれか
entityオブジェクトの選択そのオブジェクト自体。すぐ使えます
sceneシーンの選択シーンのオブジェクト
resourceリソースの選択そのまま渡せる参照
arrayリスト配列
group見出し付きのブロック入れ子のオブジェクト

覚えておくとよいもの:パネル用の labelhelp、本物の上下限である minmax、手入力での 超過は許しつつスライダーの端を決める softMax、ほかの項目が特定の値のときだけ項目を出す showWhen、そして長い一覧を整える group

defineProps はそのまま書き下す必要があります

エディターは script を実行せずに設定を読むので、直接見えている必要があります。変数から組み立てたり、 関数から返したりしてはいけません。

値は毎回読み直されるので、props.speed は常にいまインスペクターにある値を反映します。

import する

import { TransformComponent } from '@was/engine';
import helpers from 'Scripts/helpers';

エンジンの component クラスと、パスで指定したほかのリソースを import できます。別の script ならその export、patch ならコンパイル済みモジュール、それ以外なら ctx.spawnctx.audio.play に渡せる参照が得られます。

リソースの名前を変えたり移動したりすると、これらの import は自動で追随します。

サンドボックスで使えるもの、使えないもの

script はページから隔離されて動きます。重い script が描画を止めてしまわないためです。したがって、 次のものは使えません

  • windowdocument・DOM
  • fetchlocalStorage、あらゆるネットワークアクセス
  • 描画系のライブラリ。シーンは描くのではなく component を通じて変えます
  • ブラウザーのタイマー。代わりに ctx.tick を使います

代わりに使うもの:UI には インターフェースカード、音には ctx.audio、 何かを保持するには ctx.store と globals。

script が始まるとき、止まるとき

インスタンスは、そのオブジェクトが生きていて画面にあるとき(上位に無効なものがなく、シーンが アクティブ)に作られ、そうでなくなったときに破棄されます。

on-launch は作られた直後に発火するので、取り逃すことはありません。init から返す関数が 後片付けです。購読の解除、音の停止、状態のクリアに使ってください。

script はエディターでは動かず、ホットリロードもありません

エディターはシーンを描きますが、ロジックは実行しません。編集したら Preview を開いてください。 動いている体験は、次に再起動したときに編集後の script を読み込みます。


次へ: script はどこに置くか