Die ctx-API
Über ctx arbeitet Ihr Skript mit der laufenden Szene. Auf dieser Seite steht alles, was damit
möglich ist.
init übergibt ctx an Ihr Skript. Die API ist absichtlich klein. Wenn Sie hier etwas nicht
finden, suchen Sie nach einem eingebauten Schritt, der es erledigt.
Wo Sie sind
| Name | Was es ist |
|---|---|
ctx.entity | das Objekt, an dem dieses Skript hängt |
ctx.scene | die Szene, in der das Objekt liegt |
ctx.space | der Raum — die ganze Welt, in der sich der Besucher befindet |
Frames und Trigger
ctx.tick((dt, t) => {}); // jeden Frame; dt und t in Sekunden
ctx.effect(() => {}); // läuft erneut, wenn sich Gelesenes ändert; darf eine Aufräumfunktion zurückgeben
ctx.on(trigger, (payload) => {}); // auf einen Trigger reagieren
ctx.emit(trigger, payload); // einen Trigger von diesem Objekt senden
Die meisten Trigger heißen hier genauso wie in Ereignissen. Drei Eingabe-Trigger mit Zweigen kommen in einem Skript als getrennte Trigger an:
- Taste (
on-key) kommt alson-keydownundon-keyupan; - Druck auf das Objekt (
on-mouse) kommt alson-mousedown,on-mouseupundon-clickan; - Hover (
on-hover) kommt alson-hoverundon-bluran.
Die Trigger für Zeit, Entfernung, Sichtbarkeit, Signale und Variablen funktionieren nur in
Ereignissen. Nutzen Sie in einem Skript stattdessen ctx.tick, ctx.subscribeGlobal und
ctx.handleMessage.
Diese Trigger liefern nützliche Daten:
| Trigger | Was Sie bekommen |
|---|---|
on-keydown · on-keyup | { code, ctrl, shift, alt, meta } |
on-state-active · on-state-inactive | { stateId } |
on-collide | { other } — womit Sie zusammengestoßen sind |
on-divkit-action | { id, … } — welche Schaltfläche |
on-game-control | { state } — Stehen, Gehen, Rennen oder Springen |
on-drag · on-pinch · on-rotate | { dx, dy } · { scale } · { angle } |
on-vps-localized | wo sich der Besucher tatsächlich befindet |
on-launch kommt an, sobald Ihr Skript startet. Sie können es also nicht verpassen.
Objekte finden
ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // das erste Objekt mit diesen Komponenten
ctx.query(LightComponent, TagsComponent); // alle solchen Objekte
ctx.all();
Objekte erzeugen und entfernen
ctx.create({ name: 'Bullet', parent, components: [] });
ctx.spawn(props.bulletModel, { parent });
ctx.destroy(entity);
spawn ist der einfache Weg. Geben Sie eine Ressource an, und es fügt die passenden Komponenten
für Sie hinzu:
- Ein Modell wird zu einem Modellobjekt.
- Ein Bild wird zu einer flachen Ebene mit dem Bild darauf.
- Ein Klang wird zu einer Audioquelle.
Eingebaute Schritte ausführen
ctx.step('set_visibility', { visible: false }, { targets: [enemy] });
ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// Änderungen hier drin laufen fließend ab, statt zu springen
});
targets wählt die Objekte, auf die der Schritt wirkt, wie das Feld Zielobjekt eines
Ereignisses. Ohne targets wirkt der Schritt auf das Objekt dieses Skripts.
ctx.step führt jeden der eingebauten Schritte
aus, dieselben, die auch Ihre Ereignisse nutzen. Animationen, Zustandswechsel und Szenenübergänge
sind nur einen Aufruf entfernt.
Navigation
Schicken Sie den Besucher in eine andere Szene oder in einen anderen Raum:
ctx.openScene(sceneOrId);
await ctx.openSpace(spaceRefOrId);
ctx.scenes();
Eingabe
Es gibt zwei Wege, Eingaben zu lesen.
Benannte Aktionen nutzen die Tastenbelegung des Projekts. Ändert ein Besucher seine Tasten, funktioniert Ihr Skript trotzdem:
ctx.input.pressed('jump');
ctx.input.justPressed('fire');
ctx.input.axis('moveX');
Rohe Tasten lesen die Tastatur direkt. Ein Tastendruck gilt genau einen Frame lang, lesen Sie
ihn also innerhalb von ctx.tick:
ctx.keyboard.down('KeyW');
ctx.keyboard.press('Space');
ctx.keyboard.press('ArrowLeft', { every: 200 }); // automatisch wiederholen, alle 200 ms
ctx.keyboard.axis('KeyA', 'KeyD'); // -1, 0 oder 1
Eine benannte Aktion ignoriert Modifikatoren, die sie nicht nennt. Wer Shift zum Sprinten hält, blockiert also „vorwärts“ nicht. Bei einem Tasten-Trigger ist es umgekehrt: Ein Modifikator, der nicht zu seiner Taste gehört, darf nicht gedrückt sein.
Verliert das Fenster den Fokus, gelten alle gehaltenen Tasten als losgelassen. So bleibt keine Taste hängen.
Kamera
ctx.camera.entity(); // das aktive Kameraobjekt
ctx.camera.setActive(target); // Kamera wechseln; null kehrt zur Standardkamera zurück
ctx.camera.pose(); // { position, rotation, forward } in Weltkoordinaten
Die Transformation der Kamera funktioniert in jedem Steuerungsmodus gleich. Schreiben Sie hinein, um die Kamera zu bewegen. Lesen Sie sie, um zu sehen, wohin die Steuerung die Kamera gesetzt hat.
In den Modi orbit und firstPerson setzt die Steuerung die Rotation, eine geschriebene Rotation
wird also ersetzt. Die Position, die Sie schreiben, bleibt erhalten.
Raycasting
Ein Strahl ist eine Linie, die von einem Punkt in eine Richtung verläuft. Senden Sie einen aus, um zu finden, was auf ihm liegt.
await ctx.raycast(); // aus der Bildmitte der Kamera
await ctx.raycast({ screen: { x: 0.5, y: 0 }, all: true }); // ein Punkt auf dem Bildschirm, alle Treffer
await ctx.raycast({ origin, direction }); // ein beliebiger Strahl
await ctx.raycast({ from: entity }); // von einem Objekt aus, in seine Vorwärtsrichtung
Jeder Treffer liefert das Objekt, den Abstand sowie Punkt und Flächennormale in Weltkoordinaten. Die Treffer sind nach Nähe sortiert, einer pro Objekt.
await verwendenDie Antwort kommt erst im nächsten Frame.
Unsichtbare Objekte werden übersprungen, ebenso Objekte, die als ignoriert markiert sind, etwa Zielmarkierungen und Gizmos. Sie bekommen dann das, was dahinter liegt, statt ins Leere zu treffen.
Physik
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] });
Wo er ist, bestimmt die Physik. Setzen Sie ihn mit teleport an eine Stelle, und bewegen Sie ihn
mit Impulsen oder Kräften.
Wenn Sie einen Strahl aus Ihrem eigenen Körper heraus senden, übergeben Sie skip. Sonst trifft
der Strahl jedes Mal Ihren eigenen Körper.
Die Messwerte hinken etwa einen Frame hinterher. Für „Bewege ich mich?“ reicht das, für genaue Berechnungen nicht.
Audio
ctx.audio.play(props.hitSound.id, { at: enemy, volume: 0.6, positional: true });
play erwartet die ID des Klangs. Bei einer Klang-Einstellung aus defineProps ist das
props.hitSound.id.
Jeder Aufruf spielt einen eigenen Klang ab. Schritte, Treffer und Schüsse schneiden sich also nie gegenseitig ab. Die Audio-Komponente spielt immer nur einen Klang und bricht sich dabei selbst ab. Nutzen Sie sie deshalb nicht für Effekte.
Werte aufbewahren und teilen
Es gibt drei Wege, Werte aufzubewahren und mit anderen Skripten zu teilen:
// 1. die eigenen Werte dieses Skripts
const store = ctx.store('game', { score: { type: 'number', default: 0 } });
store.set('score', (v) => v + 1);
store.subscribe('score', (v) => {});
// 2. globale Werte — geteilt mit jedem Skript, Patch und Ereignis
ctx.setGlobal('level', 3);
ctx.getGlobal('level');
ctx.subscribeGlobal('level', (v) => {});
// 3. Nachrichten zwischen Skripten
ctx.postMessage('enemy-died', { id });
ctx.handleMessage('enemy-died', ({ id }) => {});
| Gespeichert in | Nach einem Szenenwechsel | Nach dem Wechsel in einen anderen Raum | Nach dem Neuladen |
|---|---|---|---|
store | ja | nein | nein |
| globale Werte | ja | ja | nein |
| globale Werte pro Raum | ja | nein — jeder Raum hat absichtlich seine eigenen | nein |
Nichts davon wird zwischen zwei Besuchen gespeichert. Wenn Sie einen Wert beim nächsten Mal brauchen, schicken Sie ihn selbst irgendwohin, solange der Besucher noch verbunden ist.
Plugins
Lesen und ändern Sie die eigene Komponente eines Plugins, und nutzen Sie die Trigger und Schritte, die ein Plugin hinzufügt:
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 }) => {}); // ein Trigger des Plugins
ctx.step('acme.patrol:pause', { seconds: 2 }); // ein Schritt des Plugins
ctx.plugin hat entities(), has(e), props(e), state(e), setState, setProps,
setEnabled, add und remove. Die Typen kommen vom Plugin, Ihr Code-Editor kennt also die
Felder. Siehe Laufzeitcode.
Oberfläche
Lesen und ändern Sie Werte auf einer UI-Karte und reagieren Sie auf ihre Schaltflächen:
const ui = ctx.getDivKit(entity);
ui.get('score');
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {});
ui.onAction('restart', () => {});
Siehe UI-Karten.
Weiter: Rezepte — vollständige Skripte zum Kopieren.