Zum Hauptinhalt springen

Erstes Skript

Schreiben Sie ein kleines Skript, das ein Objekt dreht, wenn Sie es antippen. Fügen Sie dann Einstellungen zum Inspektor hinzu und sehen Sie, was ein Skript nutzen kann.

Ein Skript ist eine TypeScript-Datei, die Sie an ein Objekt hängen. Es läuft im Player. Jedes Objekt, das das Skript nutzt, bekommt eine eigene Kopie davon.

Hier ist ein vollständiges Skript. Tippen Sie das Objekt an, und es beginnt sich zu drehen:

Scripts/spinner.ts
import { TransformComponent } from '@was/engine';

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
};
});

Das Skript hat vier Teile:

TeilWas er tut
initStartet das Skript und gibt Ihnen ctx, Ihren Zugang zur laufenden Szene. Hier beginnt alles.
definePropsFügt Einstellungen zum Inspektor hinzu. Sie füllen sie für jedes Objekt aus.
ctx.onReagiert auf einen Trigger, etwa ein Antippen oder eine Kollision.
ctx.tickLäuft in jedem Frame. dt ist die Zeit seit dem letzten Frame, in Sekunden.

init und defineProps sind immer verfügbar. Sie müssen sie nie importieren. Komponentenklassen wie TransformComponent kommen aus @was/engine.

Objekte lesen und ändern​

const transform = ctx.entity.getComponent(TransformComponent);

transform.position; // einen Wert lesen
transform.$data; // alles als einfaches Objekt lesen
transform.update({ … }); // schreiben

Zwei Fehler passieren fast jedem.

Wer einen verschachtelten Wert ändert, löscht den Rest

update behält die Felder, die Sie weglassen, etwa rotation. Ein Wert, den Sie übergeben, ersetzt den alten aber vollständig. Deshalb setzt update({ position: { y: 2 } }) auch x und z auf ihren Standardwert zurück, auf null.

Kopieren Sie die Werte, die Sie behalten wollen, mit ...:

transform.update({ position: { ...transform.$data.position, y: 2 } });
Ein Material ist eine Liste von Slots

material.update({ color }) bewirkt nichts, weil color in einem Slot steckt. Ändern Sie stattdessen den Slot:

material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});

Ändern Sie $data nie direkt. Es sieht aus, als hätte es geklappt, aber die Änderung geht verloren.

Einstellungen im Inspektor​

Mit defineProps können Sie ein Skript auf zehn Objekte legen und es auf jedem anders einstellen. 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' },
});
TypDer Editor zeigtIhr Skript bekommt
numberein Feld oder einen Schiebereglereine Zahl
stringein Textfeldeinen String
booleaneinen Schaltereinen Boolean
coloreine Farbauswahleinen Farb-String
selecteine Auswahllisteeine Ihrer Optionen
entityeine Objektauswahldas Objekt selbst, direkt nutzbar
sceneeine Szenenauswahldas Szenenobjekt
resourceeine Ressourcenauswahleinen Verweis, den Sie weitergeben können
arrayeine Listeein Array
groupeinen Block mit Titelein verschachteltes Objekt

Nützliche Optionen:

  • label und help — der Text, der im Panel steht.
  • min und max — feste Grenzen.
  • softMax — wo der Schieberegler endet. Man kann trotzdem eine größere Zahl eintippen.
  • showWhen — zeigt ein Feld nur, wenn ein anderes Feld einen bestimmten Wert hat.
  • group — hält eine lange Liste von Einstellungen übersichtlich.
defineProps vollständig ausschreiben

Schreiben Sie Ihre Einstellungen direkt in den Aufruf, sonst kann der Inspektor sie nicht anzeigen. Bauen Sie sie nicht aus Variablen zusammen und geben Sie sie nicht aus einer Funktion zurück.

Ihr Skript bekommt immer den aktuellen Wert: props.speed ist genau das, was der Inspektor gerade zeigt.

Importe​

import { TransformComponent } from '@was/engine';
import helpers from 'Scripts/helpers';

Sie können die Komponentenklassen der Engine importieren. Sie können auch andere Ressourcen über ihren Pfad importieren:

  • Ein anderes Skript liefert, was es exportiert.
  • Ein Patch liefert seinen Code, so wie ihn das Panel Code zeigt.
  • Alles andere liefert einen Verweis, { id, type }. Übergeben Sie ihn an ctx.spawn oder seine id an ctx.audio.play.

Wenn Sie eine Ressource umbenennen oder verschieben, werden diese Importe automatisch angepasst.

Was ein Skript nicht nutzen kann​

Ein aufwendiges Skript kann das Bild nicht ausbremsen. Folgendes steht Skripten nicht zur Verfügung:

  • window, document, das DOM;
  • fetch, localStorage oder sonst ein Netzwerkzugriff;
  • Rendering-Bibliotheken — Sie ändern die Szene über Komponenten, nicht durch Zeichnen;
  • Browser-Timer — nutzen Sie stattdessen ctx.tick.

Nutzen Sie stattdessen:

  • für die Oberfläche — UI-Karten;
  • für Ton — ctx.audio;
  • zum Aufbewahren von Werten — ctx.store und globale Werte.

Wann ein Skript startet und endet​

Ein Skript startet, sobald sein Objekt aktiv im Bild ist: Nichts darüber ist deaktiviert, und seine Szene ist aktiv. Es endet, wenn das nicht mehr zutrifft.

on-launch löst aus, sobald das Skript startet. Sie verpassen es also nie.

Die Funktion, die Sie aus init zurückgeben, läuft, wenn das Skript endet. Nutzen Sie sie zum Aufräumen: Abonnements beenden, Klänge stoppen, Zustand zurücksetzen.

Skripte laufen nicht im Editor

Der Editor zeigt Ihre Szene, führt aber keine Logik aus. Bearbeiten Sie das Skript und öffnen Sie dann die Vorschau. Es gibt kein Hot Reload: Ein laufendes Projekt übernimmt Ihre Änderungen erst beim nächsten Neustart.


Weiter: Wo Skripte sitzen — an welche Ebene Sie ein Skript hängen.