Zum Hauptinhalt springen

Die ctx-API

ctx ist Ihr Griff an die laufende Szene — er wird an init übergeben, und alles Folgende hängt daran.

Er ist bewusst klein. Wenn Sie etwas suchen und hier nicht finden, ist die Antwort mit guter Wahrscheinlichkeit ein eingebauter Schritt statt einer API.

Wo Sie sind

ctx.entitydas Objekt, an dem dieses Skript hängt
ctx.scenedie Szene, zu der es gehört
ctx.spacedie Welt

Lebenszyklus und Ereignisse

ctx.tick((dt, t) => {});          // jedes Bild; dt und t in Sekunden
ctx.effect(() => {}); // läuft erneut, wenn sich Gelesenes ändert; kann ein Cleanup zurückgeben
ctx.on(trigger, (payload) => {}); // einen Auslöser abonnieren
ctx.emit(trigger, payload); // einen von diesem Objekt aus auslösen

Auslöser tragen dieselben Namen wie bei Ereignissen und Patches. Die mit nützlicher Information:

AuslöserSie bekommen
on-keydown · on-keyup{ code, ctrl, shift, alt, meta }
on-state-active · on-state-inactive{ stateId }
on-collide{ other } — was Sie getroffen haben
on-divkit-action{ id, … } — welcher Button
on-game-control{ state } — Ruhe, Bewegung, Laufen oder Sprung
on-drag · on-pinch · on-rotate{ dx, dy } · { scale } · { angle }
on-vps-localizedwo der Besucher sich als befindlich herausstellte

on-launch trifft in dem Moment ein, in dem Ihre Instanz erzeugt wird — Sie können es also nicht verpassen, weil Sie zu spät gestartet sind.

Objekte finden

ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // das erste Objekt mit diesen Komponenten
ctx.query(LightComponent, TagsComponent); // alle davon
ctx.all();

Erzeugen und entfernen

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

spawn ist das Bequeme: Reichen Sie ihm eine Ressource, und es setzt die passenden Komponenten zusammen. Ein Modell wird ein Modellobjekt, ein Bild eine texturierte Fläche, ein Klang eine Audioquelle.

Eingebautes Verhalten ausführen

ctx.step('play_animation', { presetId }, { targets: [enemy] });

ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// hier gemachte Änderungen laufen weich statt zu springen
});
Das ist die Abkürzung, die die meisten übersehen

ctx.step führt jeden der eingebauten Schritte aus — dieselben, die Ihre Ereignisse benutzen. Animation, State-Wechsel, Szenenübergänge und Transitionen sind einen Aufruf entfernt, Sie müssen sie also fast nie nachbauen.

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

Eingaben

Zwei Ebenen für zwei verschiedene Aufgaben.

Benannte Aktionen lesen die Tastenbelegungen des Projekts, sodass ein Besucher, der seine Tasten umlegt, respektiert wird:

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

Rohe Tasten lesen die Tastatur direkt. Ein Druck dauert genau ein Bild, lesen Sie sie also innerhalb von ctx.tick:

ctx.keyboard.down('KeyW');
ctx.keyboard.press('Space');
ctx.keyboard.press('ArrowLeft', { every: 200 }); // Auto-Wiederholung, in ms
ctx.keyboard.axis('KeyA', 'KeyD'); // -1, 0 oder 1
Die beiden behandeln Modifikatoren absichtlich verschieden

Eine benannte Belegung ignoriert Modifikatoren, die sie nicht nennt — mit gehaltener Shift zu sprinten darf „vorwärts" nicht abbrechen. Ein Tasten-Auslöser ist das Gegenteil: Ein Modifikator, den Sie nicht angehakt haben, heißt „darf nicht gehalten sein".

Den Fensterfokus zu verlieren löst gehaltene Tasten, es bleibt also nichts hängen.

Kamera

ctx.camera.entity();           // das aktive Kameraobjekt
ctx.camera.setActive(target); // Kamera wechseln; null stellt die Voreinstellung wieder her
ctx.camera.pose(); // { position, rotation, forward } in Weltkoordinaten

Der Transform der Kamera ist in jedem Steuermodus die Quelle der Wahrheit: Schreiben Sie hinein, um die Kamera zu bewegen, lesen Sie ihn, um zu sehen, wohin die Steuerung sie gesetzt hat. In den Modi Orbit und First-Person gehört die Ausrichtung der Steuerung, eine von Ihnen geschriebene Rotation wird also überschrieben — die Position wird respektiert.

Raycasting

await ctx.raycast();                                       // aus der Kameramitte
await ctx.raycast({ screen: { x: 0.5, y: 0 }, all: true }); // ein Bildschirmpunkt, jeder Treffer
await ctx.raycast({ origin, direction }); // ein beliebiger Strahl
await ctx.raycast({ from: entity }); // von einem Objekt aus, entlang seiner Vorwärtsachse

Jeder Treffer nennt Ihnen das Objekt, die Entfernung sowie Punkt und Flächennormale in Weltkoordinaten — nach Nähe sortiert, ein Treffer pro Objekt.

Es gibt ein Promise zurück, und das ist kein Fehler

Der Strahl wird auf der Renderseite gegen echte Geometrie geworfen, die Antwort kommt also im nächsten Bild. Unsichtbare Objekte und als zu ignorieren markierte (Fadenkreuze, Gizmos) werden übersprungen, also antwortet stattdessen, was dahinterliegt, statt dass der Strahl einen Fehlschlag meldet.

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] });
Den Transform eines dynamischen Körpers zu setzen bewirkt nichts

Die Physik besitzt seine Position. Nehmen Sie teleport zum Platzieren und Impulse oder Kräfte zum Bewegen.

Und übergeben Sie skip, wenn Sie einen Strahl aus dem Inneren des eigenen Körpers abfeuern, sonst treffen Sie jedes Mal sich selbst.

Messwerte stammen aus dem zuletzt synchronisierten Zustand und hinken etwa ein Bild hinterher — gut für „bewege ich mich?", falsch für exakte Momentanrechnungen.

Audio

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

Jeder Aufruf startet einen eigenständigen Ton — genau das, was Schritte, Einschläge und Schüsse brauchen. Die Audio-Komponente ist eine einzelne Stimme und schneidet sich selbst ab; nehmen Sie sie nicht für Effekte.

Dinge behalten

Drei Speicher, unterschiedlich weit reichend:

// 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. Globals — 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 }) => {});
Überlebt einen SzenenwechselÜberlebt den Wechsel zwischen SpacesÜberlebt ein Neuladen
storejaneinnein
Globalsjajanein
Space-bezogene Globalsjanein — bewusst isoliertnein
Nichts davon überlebt das Schließen des Tabs

Keines der drei wird zwischen Besuchen gespeichert. Muss etwas bestehen bleiben, schicken Sie es selbst irgendwohin, solange Sie noch eine Verbindung haben.

Oberfläche

const ui = ctx.getDivKit(entity);

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

Siehe Interface-Karten.


Weiter: Was Sie wirklich bauen werden — vollständige Skripte zum Kopieren.