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:
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:
| Teil | Was er tut |
|---|---|
init | Startet das Skript und gibt Ihnen ctx, Ihren Zugang zur laufenden Szene. Hier beginnt alles. |
defineProps | Fügt Einstellungen zum Inspektor hinzu. Sie füllen sie für jedes Objekt aus. |
ctx.on | Reagiert auf einen Trigger, etwa ein Antippen oder eine Kollision. |
ctx.tick | Lä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.
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 } });
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' },
});
| Typ | Der Editor zeigt | Ihr Skript bekommt |
|---|---|---|
number | ein Feld oder einen Schieberegler | eine Zahl |
string | ein Textfeld | einen String |
boolean | einen Schalter | einen Boolean |
color | eine Farbauswahl | einen Farb-String |
select | eine Auswahlliste | eine Ihrer Optionen |
entity | eine Objektauswahl | das Objekt selbst, direkt nutzbar |
scene | eine Szenenauswahl | das Szenenobjekt |
resource | eine Ressourcenauswahl | einen Verweis, den Sie weitergeben können |
array | eine Liste | ein Array |
group | einen Block mit Titel | ein verschachteltes Objekt |
Nützliche Optionen:
labelundhelp— der Text, der im Panel steht.minundmax— 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 ausschreibenSchreiben 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 anctx.spawnoder seineidanctx.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,localStorageoder 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.storeund 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.
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.