Ihr erstes Skript
Ein Skript ist eine TypeScript-Datei, die Sie an ein Objekt hängen. Es läuft im Player, einmal für jedes Objekt, das es benutzt.
Hier ist ein vollständiges — tippen Sie das Objekt an, und es beginnt sich zu drehen:
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 () => {
// optional: aufräumen, wenn dieses Objekt verschwindet
};
});
Vier Dinge passieren hier:
init | bekommt ctx, Ihren Griff an die laufende Szene. Alles beginnt hier |
defineProps | deklariert Einstellungen, die der Editor im Inspektor zeigt, je Objekt ausgefüllt |
ctx.on | abonniert einen Auslöser — dieselben, die Ereignisse und Patches benutzen |
ctx.tick | läuft jedes Bild, mit dt in Sekunden |
init und defineProps sind immer verfügbar — Sie importieren sie nie.
Objekte lesen und schreiben
const transform = ctx.entity.getComponent(TransformComponent);
transform.position; // einen Wert lesen
transform.$data; // das Ganze als schlichtes Objekt lesen
transform.update({ … }); // schreiben
Zwei Fallen, und es sind die beiden, in die alle tappen:
update({ position: { y: 2 } }) setzt auch x und z auf null — Sie haben eine ganze neue
Position übergeben, in der nur y ausgefüllt war.
Spreaden Sie, was Sie behalten wollen:
transform.update({ position: { ...transform.$data.position, y: 2 } });
material.update({ color }) tut überhaupt nichts, weil color in einem Slot lebt und nicht ganz
oben. Schreiben Sie den Slot:
material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
Und weisen Sie niemals direkt in $data zu. Es sieht aus, als hätte es funktioniert, und die
Änderung wird verworfen.
Einstellungen im Inspektor
defineProps ist das, was ein Skript wiederverwendbar macht: dasselbe Skript an zehn Objekten, an
jedem anders konfiguriert, ganz ohne Kopieren und Einfügen.
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' },
});
| Typ | Der Editor zeigt | Ihr Skript bekommt |
|---|---|---|
number | ein Feld oder einen Regler | eine Zahl |
string | ein Textfeld | eine Zeichenkette |
boolean | einen Schalter | einen Boolean |
color | eine Farbauswahl | eine Farbzeichenkette |
select | ein Dropdown | eine Ihrer Optionen |
entity | eine Objektauswahl | das Objekt selbst, einsatzbereit |
scene | eine Szenenauswahl | das Szenenobjekt |
resource | eine Ressourcenauswahl | einen Verweis zum Weiterreichen |
array | eine Liste | ein Array |
group | einen betitelten Block | ein verschachteltes Objekt |
Wissenswert: label und help fürs Panel, min und max für echte Grenzen, softMax dafür, wo
ein Regler endet, ohne größere getippte Werte zu verbieten, showWhen, um ein Feld nur bei einem
bestimmten Wert eines anderen zu zeigen, und group, um eine lange Liste ordentlich zu halten.
defineProps muss wörtlich hingeschrieben seinDer Editor liest Ihre Einstellungen, ohne das Skript auszuführen, er muss sie also direkt sehen — nicht aus Variablen gebaut oder von einer Funktion zurückgegeben.
Werte werden jedes Mal frisch gelesen, props.speed spiegelt also immer, was gerade im Inspektor
steht.
Dinge importieren
import { TransformComponent } from '@was/engine';
import helpers from 'Scripts/helpers';
Sie können die Komponentenklassen der Engine importieren und andere Ressourcen über ihren Pfad:
Ein anderes Skript gibt Ihnen seine Exporte, ein Patch sein kompiliertes Modul, und alles andere
einen Verweis, den Sie an ctx.spawn oder ctx.audio.play weiterreichen können.
Benennen Sie eine Ressource um oder verschieben sie, aktualisieren sich diese Importe von selbst.
Was die Sandbox bietet — und was nicht
Skripte laufen von der Seite isoliert, was verhindert, dass ein schweres Skript das Rendern blockiert. Deshalb sind nicht verfügbar:
window,document, das DOM;fetch,localStorage, Netzwerkzugriff jeder Art;- jede Rendering-Bibliothek — Sie ändern die Szene über Komponenten, nicht durch Zeichnen;
- Browser-Timer — nehmen Sie stattdessen
ctx.tick.
Was Sie stattdessen nutzen: Interface-Karten für Oberflächen,
ctx.audio für Ton, ctx.store und Globals, um Dinge zu behalten.
Wann Ihr Skript startet und stoppt
Eine Instanz entsteht, wenn ihr Objekt lebt und auf dem Bildschirm ist — nichts darüber deaktiviert, seine Szene aktiv — und wird zerstört, sobald das nicht mehr gilt.
on-launch feuert, sobald sie erzeugt wurde, Sie verpassen es also nie. Die Funktion, die Sie aus
init zurückgeben, ist Ihr Aufräumen: abmelden, Töne stoppen, Zustand leeren.
Der Editor zeichnet Ihre Szene, führt aber keine Logik aus. Bearbeiten, dann Preview öffnen. Ein laufendes Erlebnis übernimmt ein geändertes Skript beim nächsten Neustart.
Weiter: Wo ein Skript wohnt