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

はじめてのスクリプト

タップするとオブジェクトが回る、小さなスクリプトを書いてみましょう。そのあとで、インスペクターに設定を追加し、スクリプトで何が使えるかを見ていきます。

スクリプトは、オブジェクトに付ける TypeScript ファイルです。プロジェクトを再生するプレイヤーの中で動きます。スクリプトを使うオブジェクトは、それぞれ自分専用のコピーを持ちます。

完成したスクリプトです。オブジェクトをタップすると回り始めます。

Scripts/spinner.ts
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 つあります。

入れ子の値を 1 つ変えると、残りが消えます

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、DOM
  • fetch、localStorage、その他のネットワークアクセス
  • 描画ライブラリ(シーンは描画ではなく、コンポーネントを通して変更します)
  • ブラウザのタイマー(代わりに ctx.tick を使います)

代わりに次のものを使ってください。

  • インターフェースには UI カード
  • 音には ctx.audio
  • 値を保存するには ctx.store とグローバル

スクリプトの開始と停止​

スクリプトは、オブジェクトが画面上で動いている状態になると開始します。つまり、上の階層に無効なものがなく、シーンが有効になっているときです。その条件を満たさなくなると停止します。

on-launch はスクリプトが開始するとすぐに発生するので、取りこぼすことはありません。

init から返した関数は、スクリプトが停止するときに実行されます。購読の解除、音の停止、ステートのクリアなどの後片付けに使ってください。

スクリプトはエディターでは動きません

エディターはシーンを表示しますが、ロジックは何も実行しません。スクリプトを編集したら、プレビュー(Preview)を開いてください。ホットリロードはありません。実行中のプロジェクトには、次に再起動したときに変更が反映されます。


次へ: スクリプトを置く場所 — スクリプトをどの階層に付けるか。