ctx の API
ctx は動いているシーンへの取っ手です。init に渡され、以下のすべてはそこにぶら下がっています。
意図的に小さく作ってあります。探しものがここに見当たらないなら、答えは API ではなく 組み込みのステップである可能性が高いです。
いまどこにいるか
ctx.entity | この script が付いているオブジェクト |
ctx.scene | それが属するシーン |
ctx.space | ワールド |
ライフサイクルとイベント
ctx.tick((dt, t) => {}); // 毎フレーム。dt と t は秒
ctx.effect(() => {}); // 読んだものが変わると再実行。後片付けを返せます
ctx.on(trigger, (payload) => {}); // トリガーを購読する
ctx.emit(trigger, payload); // このオブジェクトから発火させる
トリガー名は、イベントや patch と同じです。有用な情報を運ぶのは次のものです。
| トリガー | 受け取れるもの |
|---|---|
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 } — 待機・移動・走行・ジャンプ |
on-drag · on-pinch · on-rotate | { dx, dy } · { scale } · { angle } |
on-vps-localized | 来場者が実際にどこにいたか |
on-launch はインスタンスが作られたその瞬間に届くので、遅れて始めて取り逃す心配はありません。
オブジェクトを探す
ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // これらの component を持つ最初のオブジェクト
ctx.query(LightComponent, TagsComponent); // 該当するものすべて
ctx.all();
作る・消す
ctx.create({ name: 'Bullet', parent, components: [] });
ctx.spawn(props.bulletModel, { parent });
ctx.destroy(entity);
便利なのは spawn です。リソースを渡すと、それにふさわしい component を組み立ててくれます。
モデルはモデルのオブジェクトに、画像はテクスチャを貼った平面に、音は音源になります。
組み込みの挙動を実行する
ctx.step('play_animation', { presetId }, { targets: [enemy] });
ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// ここで行った変更は、パチッと切り替わらずになめらかに変化します
});
ctx.step は組み込みのステップをどれでも実行します。イベントが
使っているのと同じものです。アニメーション、state の切り替え、シーン遷移、トランジションが
呼び出し 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 }); // 自動リピート(ミリ秒)
ctx.keyboard.axis('KeyA', 'KeyD'); // -1・0・1
名前付きの割り当ては、言及していない修飾キーを無視します。Shift を押しながら走っても「前進」が 打ち消されてはいけないからです。キーのトリガーは逆で、チェックしなかった修飾キーは 「押されていてはいけない」という意味になります。
ウィンドウのフォーカスが外れると押されているキーはクリアされるので、押しっぱなしで固まることは ありません。
カメラ
ctx.camera.entity(); // アクティブなカメラのオブジェクト
ctx.camera.setActive(target); // カメラを切り替える。null で既定に戻します
ctx.camera.pose(); // ワールド空間での { position, rotation, forward }
どの操作モードでも、正となるのはカメラの transform です。動かすにはそこへ書き込み、操作系が どこへ置いたかを見るにはそこを読みます。orbit と first-person では向きは操作系のものなので、 書き込んだ回転は上書きされます。位置は尊重されます。
レイキャスト
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 ヒットです。
レイは描画側の実際のジオメトリに対して飛ばされるので、答えは次のフレームに届きます。見えない オブジェクトや、無視すると印を付けたもの(照準やギズモ)は飛ばされるので、レイが「外れ」を 返すのではなく、その後ろにあるものが答えます。
物理
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] });
位置は物理が所有しています。配置には teleport、移動にはインパルスかフォースを使ってください。
そして、自分のボディの内側からレイを飛ばすときは skip を渡してください。でないと毎回、自分に
当たります。
読み取り値は最後に同期された状態から来るので、1 フレームほど遅れます。「いま動いているか?」には 十分ですが、正確な瞬間値の計算には向きません。
音
ctx.audio.play(props.hitSound, { at: enemy, volume: 0.6, positional: true });
呼び出しごとに独立した音が始まります。足音・衝撃音・銃声にはまさにこれが必要です。audio の component は声が 1 つで、自分自身を途切れさせます。効果音には使わないでください。
何かを保持する
届く範囲の違う、3 つの置き場があります。
// 1. この script 自身の値
const store = ctx.store('game', { score: { type: 'number', default: 0 } });
store.set('score', (v) => v + 1);
store.subscribe('score', (v) => {});
// 2. globals — すべての script・patch・イベントと共有
ctx.setGlobal('level', 3);
ctx.getGlobal('level');
ctx.subscribeGlobal('level', (v) => {});
// 3. script 間のメッセージ
ctx.postMessage('enemy-died', { id });
ctx.handleMessage('enemy-died', ({ id }) => {});
| シーン切り替えを越える | space 切り替えを越える | リロードを越える | |
|---|---|---|---|
store | はい | いいえ | いいえ |
| globals | はい | はい | いいえ |
| space スコープの globals | はい | いいえ(意図的に分離) | いいえ |
3 つとも、訪問をまたいで保存されることはありません。残す必要があるものは、接続があるうちに 自分でどこかへ送ってください。
インターフェース
const ui = ctx.getDivKit(entity);
ui.get('score');
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {});
ui.onAction('restart', () => {});
次へ: 実際に作ることになるもの — そのままコピーできる完成した script。