Zum Hauptinhalt springen

Die gemeinsamen Pakete

Editor, Runtime und Ihr eigener Code sind aus denselben Paketen gebaut, und mehrere davon stehen Ihnen offen. Welche, hängt davon ab, wo Ihr Code läuft.

PaketGibt IhnenIm SkriptIm PluginIn der Komponenten-Erweiterung
@was/ecsEntities, Komponenten, die Welt
@was/enginedie Komponentenklassen
@was/signalsdas Reaktivitätssystem
@was/svdtSchemata und Validierung
@was/utilskleine Helfer
@was/uidie Komponentenbibliothek des Editors
@was/iconsden Symbolsatz des Editors
reactReact 19
Skripte bekommen Reaktivität über ctx.effect

Ein Skript kann das Signals-Paket nicht direkt importieren — ctx.effect ist derselbe Mechanismus, mit der Lebensdauer für Sie verwaltet: Ein Effekt stirbt mit seinem Objekt, statt zu lecken.

@was/signals — das Reaktivitätssystem

Darauf ist die ganze Plattform gebaut: Werte, die wissen, wer sie liest, sodass eine Änderung genau das aktualisiert, was von ihr abhing, und sonst nichts.

import { signal, computed, effect, batch, untracked } from '@was/signals';

const score = signal(0);
const doubled = computed(() => score.value * 2);

const stop = effect(() => {
render(score.value); // läuft nur erneut, wenn score sich ändert
});

batch(() => {
score.value += 1;
score.value += 1; // Effekte laufen einmal, nicht zweimal
});

stop();
FunktionTut
signal(v)ein beobachtbarer Wert; .value wird gelesen und geschrieben
computed(fn)ein abgeleiteter Wert, nur neu berechnet, wenn sich Gelesenes ändert
effect(fn)läuft jetzt und wieder, wann immer sich Gelesenes ändert; gibt einen Stopper zurück
batch(fn)Schreibvorgänge gruppieren, damit Beobachter am Ende einmal laufen
untracked(fn)lesen, ohne zur Abhängigkeit zu werden
ref(obj)ein ganzes Objekt reaktiv machen, bis in seine Blätter
snapshot(obj)eine schlichte, nicht reaktive Kopie
raw(obj)das zugrunde liegende Objekt, ohne Verfolgung
readonly(obj)eine Sicht, in die nicht geschrieben werden kann

Für React-Panels gibt es einen Begleiter mit useSignal, useComputed, useSignalEffect und useLiveSignal, sodass eine Komponente ganz ohne Verdrahtung aus einem Signal neu rendert.

Warum das zählt, selbst wenn Sie es nie importieren

Das ist es, was Editor und Runtime so verhalten lässt, wie sie es tun: Nichts pollt, nichts rendert vorsorglich neu, und ein Panel aktualisiert sich, weil sich die Daten geändert haben, nicht weil jemand es ihm gesagt hat. → Warum diese Engine

@was/ui — die Komponenten des Editors

Plugin-Panels und Komponenten-Erweiterungen lassen sich aus derselben Bibliothek bauen, die der Editor benutzt, sie wirken also nativ statt wie eine eingebettete Webseite. Gut dreißig Komponenten:

Accordion · Avatar · Button · Card · Checkbox · Chip · CloseButton
DimensionInput · Draggable · Dropdown · Flag · Icon · IconButton · Input
Menu · Modal · Outside · Panel · Popover · Portal · Render · Scroll
Search · Section · Segmented · Select · Skeleton · Switcher · Tabs
Toast · Toolbar · Tooltip

Dazu @was/icons für den Symbolsatz.

Das Stylesheet ist vorgebaut

Panels benutzen einen festen Satz Utility-Klassen, der mit der Bibliothek ausgeliefert wird. Beliebige Werte wie text-[13px] sind nicht dabei — nehmen Sie ein Inline-style für Größen außerhalb der Skala.

@was/ecs — das Weltmodell

Entity, Component, Space. Ein Plugin liest und ändert eine Szene mit genau derselben API, die die Engine intern benutzt; es gibt keine getrennte, schwächere „Plugin-API".

Space — die Welt

AufrufGibt zurück
getEntity(id)eine Entity, oder nichts
hasEntity(id)ob sie existiert
getEntities()alle
queryEntities(A, B, …)jede Entity, die all diese Komponenten trägt
makeQueryEntities(A, B, …)dieselbe Abfrage, vorgebaut, zur wiederholten Nutzung
createEntity(id?, components?)eine neue Entity, der Welt hinzugefügt
addEntity(…e) · removeEntity(…e)eine hineinlegen oder herausnehmen
getSystem(S) · hasSystem(S)an ein System herankommen
execute()ein Bild ausführen
queryEntities ist das, wonach Sie greifen sollten

Dahinter steckt ein Index, „alles mit einem Licht und einem Transform" zu erfragen ist also billig. getEntities() ist nicht dasselbe — es reicht Ihnen die ganze Welt und lässt Sie filtern.

Entity — ein Ding in der Welt

AufrufTut
getComponent(Type)eine Komponente, oder null
getComponents(A, B)mehrere auf einmal, in dieser Reihenfolge
hasComponent(A, B)ob sie alle davon trägt
addComponent(…c)hinzufügen; einen bereits vorhandenen Typ zu ergänzen wird ignoriert
removeComponent(…c)entfernen, per Klasse oder Instanz
component(fn)fn für jede Komponente ausführen, jetzt und künftig
clone() · clean()kopieren oder bis aufs Gerippe abräumen
.id · .componentsihre Id und ihre Komponenten nach Typ

Component — die Daten

AufrufTut
.x oder get('x')ein Feld lesen; in einem Effekt abonniert das zugleich
$datadas Ganze als schlichtes Objekt
$rawDatadas gespeicherte Objekt, ohne Abo — schreibgeschützt
update({ … })der einzige Weg zu schreiben
updateAt(path, value)tief in eine große Komponente schreiben, ohne alles neu zu validieren
version(field?)ein Zähler, der bei Änderungen tickt, damit Sie von einem Feld abhängen können, ohne es zu lesen
reset(data?)zurück auf die Voreinstellungen
clone()eine Kopie
version() ist der Trick hinter der Leistung bei großen Listen

Einen großen Wert zu lesen abonniert Sie auf jedes Blatt darin. Stattdessen von seinem Versionszähler abzuhängen heißt, Sie erfahren, dass er sich geändert hat, ohne alles zu beobachten — so übersteht ein Panel eine Liste mit zwanzigtausend Einträgen.

@was/engine — die Komponentenklassen

Jeder Komponententyp als Klasse: TransformComponent, MaterialComponent, RigidBodyComponent und die übrigen. Sie importieren die Klasse und reichen sie an getComponent, hasComponent oder query weiter.

Die vollständige Liste mit jedem Feld: Komponenten-Referenz.

@was/svdt — Schemata

Die Validierungsschicht, mit der Komponenten beschrieben werden. Kompiliert statt interpretiert — deshalb kostet das Parsen auf dem Animationspfad nichts.

import { s, compile } from '@was/svdt';

const schema = s.object({ speed: s.f64(1), name: s.string('') });
const codec = compile(schema); // einmal bei der Deklaration kompilieren — nie pro Aufruf
const value = codec.parse(input);

Eine Form beschreiben

Zahlen: s.f32 s.f64 s.i8 s.u8 s.i16 s.u16 s.i32 s.u32, jede mit einer Voreinstellung. Skalare: s.bool, s.string, s.color, s.literal, s.enum, s.unknown, s.ref. Vektoren: s.vec2, s.vec3, s.mat4. Zusammengesetzt: s.object, s.variant, s.array, s.record, s.union, s.preprocess, s.lazy.

An jedes davon anhängbar: .default(v), .optional(), .nullable(), .min(n), .max(n), .int().

Was ein kompilierter Codec liefert

AufrufTut
parse(input)validieren und normalisieren, bei schlechter Eingabe werfen
safeParse(input)dasselbe, gibt Erfolg oder Fehler zurück, statt zu werfen
parseAt(path, v)ein Feld validieren, ohne den Rest anzufassen
equals(a, b)Tiefenvergleich, für diese Form erzeugt
diff(a, b)was sich geändert hat
apply(target, p)ein Diff anwenden
invert(p)eines umkehren — die Grundlage des Rückgängigmachens
pack · unpackin eine kompakte Binärform und zurück

Und um ein Schema statt Daten zu untersuchen: introspect, keys, requiredKeys.

@was/utils

Kleinigkeiten, die überall gebraucht werden: pick, omit, assign, keys, values, entries, capitalize, basename, extname, dispose (Aufräumfunktionen zusammenkleben) und die MIME-Helfer hinter der Upload-Prüfung.


Weiter: Glossar