はじめてのスクリプト
タップするとオブジェクトが回る、小さなスクリプトを書いてみましょう。そのあとで、インスペクターに設定を追加し、スクリプトで何が使えるかを見ていきます。
スクリプトは、オブジェクトに付ける TypeScript ファイルです。プロジェクトを再生するプレイヤーの中で動きます。スクリプトを使うオブジェクトは、それぞれ自分専用のコピーを持ちます。
完成したスクリプトです。オブジェクトをタップすると回り始めます。
import { TransformComponent } from '@was/engine';
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 を渡します。ctx は、動いているシーンにアクセスするための入り口です。すべてはここから始まります。 |
defineProps | インスペクターに設定を追加します。値はオブジェクトごとに入力します。 |
ctx.on | タップや衝突などのトリガーに反応します。 |
ctx.tick | 毎フレーム実行されます。dt は前のフレームからの経過時間で、単位は秒です。 |
init と defineProps はいつでも使えます。インポートする必要はありません。TransformComponent などのコンポーネントクラスは、@was/engine からインポートします。
オブジェクトを読み書きする
const transform = ctx.entity.getComponent(TransformComponent);
transform.position; // 値を 1 つ読む
transform.$data; // 全体をただのオブジェクトとして読む
transform.update({ … }); // 書き込む
ほとんどの人が一度はつまずく間違いが 2 つあります。
update は、rotation など、渡さなかったフィールドをそのまま残します。ただし、渡した値は古い値を丸ごと置き換えます。そのため、update({ position: { y: 2 } }) と書くと、x と z も初期値の 0 に戻ります。
残したい値は ... でコピーしてください。
transform.update({ position: { ...transform.$data.position, y: 2 } });
color はスロットの中にあるので、material.update({ color }) では何も起きません。スロットのほうを変更してください。
material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
$data を直接変更しないでください。うまくいったように見えても、変更は失われます。
インスペクターの設定
defineProps を使えば、1 つのスクリプトを 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' },
});
| 型 | エディターの表示 | スクリプトが受け取るもの |
|---|---|---|
number | 入力欄かスライダー | 数値 |
string | テキスト入力欄 | 文字列 |
boolean | スイッチ | 真偽値 |
color | カラーピッカー | 色の文字列 |
select | ドロップダウン | 指定した選択肢のどれか |
entity | オブジェクトの選択欄 | すぐに使えるオブジェクトそのもの |
scene | シーンの選択欄 | シーンのオブジェクト |
resource | リソースの選択欄 | ほかの処理に渡せる参照 |
array | リスト | 配列 |
group | 見出し付きのブロック | 入れ子のオブジェクト |
便利なオプションです。
labelとhelp:パネルに表示するテキスト。minとmax:絶対的な上限と下限。softMax:スライダーの終わりの値。これより大きい数値も手入力できます。showWhen:別のフィールドが特定の値のときだけ、このフィールドを表示します。group:設定が多いときに、すっきりまとめます。
defineProps は省略せずに書いてください設定は呼び出しの中に直接書いてください。そうしないと、インスペクターに表示されません。変数から組み立てたり、関数から返したりしないでください。
スクリプトは常に最新の値を受け取ります。props.speed は、いまインスペクターに表示されている値です。
インポート
import { TransformComponent } from '@was/engine';
import helpers from 'Scripts/helpers';
エンジンのコンポーネントクラスをインポートできます。ほかのリソースも、パスを指定してインポートできます。
- スクリプトなら、そのスクリプトがエクスポートしているものを受け取ります。
- パッチなら、Code パネルに表示されるのと同じコードを受け取ります。
- それ以外なら、参照
{ id, type }を受け取ります。参照はctx.spawnに渡せます。ctx.audio.playには、そのidを渡します。
リソースの名前を変えたり移動したりすると、これらのインポートは自動で更新されます。
スクリプトで使えないもの
重いスクリプトがあっても、映像が遅くなることはありません。スクリプトでは、次のものは使えません。
window、document、DOMfetch、localStorage、その他のネットワークアクセス- 描画ライブラリ(シーンは描画ではなく、コンポーネントを通して変更します)
- ブラウザのタイマー(代わりに
ctx.tickを使います)
代わりに次のものを使ってください。
- インターフェースには UI カード
- 音には
ctx.audio - 値を保存するには
ctx.storeとグローバル
スクリプトの開始と停止
スクリプトは、オブジェクトが画面上で動いている状態になると開始します。つまり、上の階層に無効なものがなく、シーンが有効になっているときです。その条件を満たさなくなると停止します。
on-launch はスクリプトが開始するとすぐに発生するので、取りこぼすことはありません。
init から返した関数は、スクリプトが停止するときに実行されます。購読の解除、音の停止、ステートのクリアなどの後片付けに使ってください。
エディターはシーンを表示しますが、ロジックは何も実行しません。スクリプトを編集したら、プレビュー(Preview)を開いてください。ホットリロードはありません。実行中のプロジェクトには、次に再起動したときに変更が反映されます。
次へ: スクリプトを置く場所 — スクリプトをどの階層に付けるか。