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

ctx API

ctx を使うと、スクリプトから実行中のシーンを操作できます。このページでは、ctx でできることをすべて紹介します。

init がスクリプトに ctx を渡します。API はあえて小さくしています。ここで見つからない機能は、それを実行する組み込みのステップがないか探してください。

今いる場所​

名前内容
ctx.entityこのスクリプトが付いているオブジェクト
ctx.sceneそのオブジェクトがあるシーン
ctx.spaceスペース。見る人がいるワールド全体です

フレームとトリガー​

ctx.tick((dt, t) => {});          // 毎フレーム。dt と t の単位は秒
ctx.effect(() => {}); // 読んだものが変わると再実行。後片付け用の関数を返してもよい
ctx.on(trigger, (payload) => {}); // トリガーに反応する
ctx.emit(trigger, payload); // このオブジェクトからトリガーを送る

ほとんどのトリガーの名前は、イベントと同じです。ブランチを持つ 3 つの入力トリガーは、スクリプトでは別々のトリガーとして届きます。

  • Key(on-key)は、on-keydown と on-keyup として届きます。
  • Press on the object(on-mouse)は、on-mousedown、on-mouseup、on-click として届きます。
  • Hover(on-hover)は、on-hover と on-blur として届きます。

時間、距離、表示状態、シグナル、変数のトリガーは、イベントでしか使えません。スクリプトでは、代わりに ctx.tick、ctx.subscribeGlobal、ctx.handleMessage を使います。

次のトリガーは、便利なデータを渡してくれます。

トリガー受け取るもの
on-keydown · on-keyup{ code, ctrl, shift, alt, meta }
on-state-active · on-state-inactive{ stateId }
on-collide{ other }:ぶつかった相手
on-divkit-action{ id, … }:押されたボタン
on-game-control{ state }:idle、move、run、jump のどれか
on-drag · on-pinch · on-rotate{ dx, dy } · { scale } · { angle }
on-vps-localized見る人が実際にいた場所

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

オブジェクトを探す​

ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // これらのコンポーネントを持つ最初のオブジェクト
ctx.query(LightComponent, TagsComponent); // 該当するものすべて
ctx.all();

オブジェクトを作成・削除する​

ctx.create({ name: 'Bullet', parent, components: [] });
ctx.spawn(props.bulletModel, { parent });
ctx.destroy(entity);

簡単なのは spawn です。リソースを渡すと、必要なコンポーネントを付けてくれます。

  • モデルは、モデルのオブジェクトになります。
  • 画像は、その画像を貼った平らな面になります。
  • サウンドは、音源になります。

組み込みのステップを実行する​

ctx.step('set_visibility', { visible: false }, { targets: [enemy] });

ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// ここで行った変更は、一気に切り替わらず、なめらかにアニメーションする
});

targets は、ステップが作用するオブジェクトを選びます。イベントの Target object フィールドと同じです。指定しなければ、ステップはこのスクリプトのオブジェクトに作用します。

自分で書く前に、ステップを使いましょう

ctx.step は、組み込みのステップをどれでも実行できます。イベントで使うのと同じステップです。アニメーション、ステートの変更、シーンの切り替えが、1 回の呼び出しでできます。

見る人を別のシーンやスペースに移動させます。

ctx.openScene(sceneOrId);
await ctx.openSpace(spaceRefOrId);
ctx.scenes();

入力​

入力の読み取り方は 2 つあります。

名前付きアクションは、プロジェクトのキー割り当てを使います。見る人がキーを変更しても、スクリプトはそのまま動きます。

ctx.input.pressed('jump');
ctx.input.justPressed('fire');
ctx.input.axis('moveX');

生のキーは、キーボードを直接読みます。「押された」という判定はちょうど 1 フレームだけ有効なので、ctx.tick の中で読んでください。

ctx.keyboard.down('KeyW');
ctx.keyboard.press('Space');
ctx.keyboard.press('ArrowLeft', { every: 200 }); // 200 ms ごとに自動でリピート
ctx.keyboard.axis('KeyA', 'KeyD'); // -1、0、1 のどれか
修飾キーの扱いは、2 つの方法で違います

名前付きアクションは、指定していない修飾キーを無視します。そのため、Shift を押してダッシュしていても「前進」は止まりません。キーのトリガーはその逆で、そのキーの一部ではない修飾キーは押されていてはいけません。

ウィンドウからフォーカスが外れると、押されているキーはすべて離されたことになるので、キーが押しっぱなしになることはありません。

カメラ​

ctx.camera.entity();           // 有効なカメラのオブジェクト
ctx.camera.setActive(target); // カメラを切り替える。null で初期のカメラに戻る
ctx.camera.pose(); // ワールド座標での { position, rotation, forward }

カメラのトランスフォームは、どの操作モードでも同じように働きます。カメラを動かすにはトランスフォームに書き込みます。操作によってカメラがどこに置かれたかは、トランスフォームを読めばわかります。

orbit モードと firstPerson モードでは回転を操作側が決めるので、書き込んだ回転は置き換えられます。書き込んだ位置はそのまま使われます。

レイキャスト​

レイは、ある点からある方向に伸びる線です。レイを飛ばすと、その線上にあるものがわかります。

await ctx.raycast();                                       // カメラの中心から
await ctx.raycast({ screen: { x: 0.5, y: 0 }, all: true }); // 画面上の点から。当たったものすべて
await ctx.raycast({ origin, direction }); // 好きなレイを指定
await ctx.raycast({ from: entity }); // オブジェクトから、その前方向へ

当たったものごとに、オブジェクト、距離、ワールド座標での点と面の法線を受け取れます。当たったものは近い順に並び、1 つのオブジェクトにつき 1 件です。

await を使ってください

結果は次のフレームで届きます。

見えないオブジェクトと、照準やギズモのように無視する印が付いたオブジェクトは、スキップされます。当たらなかった扱いにはならず、その後ろにあるものが返ってきます。

物理​

ctx.physics.applyImpulse(target, { x: 0, y: 5, z: 0 });
ctx.physics.applyForce(target, vec, point);
ctx.physics.setVelocity(target, vec);
ctx.physics.teleport(target, position, { rotation, keepVelocity });
ctx.physics.setGravity(vec);

ctx.physics.getSpeed(target);
ctx.physics.isSleeping(target);

await ctx.physics.raycast(from, to, { skip: [ctx.entity] });
dynamic なボディのトランスフォームを変えても、何も起きません

位置は物理が決めます。置く場所を変えるには teleport を、動かすにはインパルスか力を使ってください。

自分のボディはスキップしましょう

自分のボディの内側からレイを飛ばすときは、skip を渡してください。そうしないと、レイが毎回自分のボディに当たります。

読み取る値は、1 フレームほど遅れています。「動いているか」の確認には十分ですが、正確な計算には向きません。

音声​

ctx.audio.play(props.hitSound.id, { at: enemy, volume: 0.6, positional: true });

play には、サウンドの ID を渡します。defineProps のサウンド設定なら、props.hitSound.id です。

呼び出すたびに別の音として鳴るので、足音、打撃音、銃声が互いに途切れさせ合うことはありません。音声コンポーネントは一度に 1 つの音しか鳴らせず、自分で前の音を止めてしまうので、効果音には使わないでください。

値を保存・共有する​

値を保存し、ほかのスクリプトと共有する方法は 3 つあります。

// 1. このスクリプト専用の値
const store = ctx.store('game', { score: { type: 'number', default: 0 } });
store.set('score', (v) => v + 1);
store.subscribe('score', (v) => {});

// 2. グローバル:すべてのスクリプト、パッチ、イベントで共有する
ctx.setGlobal('level', 3);
ctx.getGlobal('level');
ctx.subscribeGlobal('level', (v) => {});

// 3. スクリプト間のメッセージ
ctx.postMessage('enemy-died', { id });
ctx.handleMessage('enemy-died', ({ id }) => {});
保存先シーンの切り替え後別のスペースに移動したあと再読み込み後
store残る残らない残らない
グローバル残る残る残らない
スペース単位のグローバル残る残らない。スペースごとに別の値を持つよう意図的にしています残らない
タブを閉じると、すべて消えます

どれも、訪問をまたいで保存されることはありません。次回も使いたい値があるなら、見る人がまだ接続している間に、自分で別の場所へ送ってください。

プラグイン​

プラグインのカスタムコンポーネントを読み書きし、プラグインが追加するトリガーとステップを使います。

const health = ctx.plugin('acme.health/health');   // '<plugin id>/<component id>'
for (const e of health.entities()) {
const max = health.props(e)!.max as number;
health.setState(e, { hp: max });
}

ctx.on('acme.patrol:reached', ({ point }) => {}); // プラグインのトリガー
ctx.step('acme.patrol:pause', { seconds: 2 }); // プラグインのステップ

ctx.plugin には、entities()、has(e)、props(e)、state(e)、setState、setProps、setEnabled、add、remove があります。型はプラグインから提供されるので、コードエディタがフィールドを把握できます。ランタイムコードを参照してください。

インターフェース​

UI カードの値を読み書きし、ボタンに反応します。

const ui = ctx.getDivKit(entity);

ui.get('score');
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {});
ui.onAction('restart', () => {});

UI カードを参照してください。


次へ: レシピ — そのままコピーして使える完成したスクリプト。