Aller au contenu principal

Votre premier script

Un script est un fichier TypeScript que vous attachez à un objet. Il s'exécute dans le lecteur, une fois pour chaque objet qui l'utilise.

En voici un complet — touchez l'objet et il se met à tourner :

Scripts/spinner.ts
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 () => {
// facultatif : faire le ménage quand cet objet disparaît
};
});

Quatre choses se passent :

initreçoit ctx, votre poignée sur la scène en cours. Tout commence ici
definePropsdéclare les réglages que l'éditeur affiche dans l'inspecteur, remplis par objet
ctx.ons'abonne à un déclencheur — les mêmes que ceux des événements et des patches
ctx.ticks'exécute à chaque image, avec dt en secondes

init et defineProps sont toujours disponibles — vous ne les importez jamais.

Lire et écrire des objets

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

transform.position; // lire une valeur
transform.$data; // lire le tout comme objet simple
transform.update({}); // écrire

Deux pièges, et ce sont ceux qui attrapent tout le monde :

Écrire une valeur imbriquée la remplace entièrement

update({ position: { y: 2 } }) met aussi x et z à zéro — vous lui avez remis une position entière avec seulement y de rempli.

Étalez ce que vous voulez garder :

transform.update({ position: { ...transform.$data.position, y: 2 } });
Un matériau est une liste de slots

material.update({ color }) ne fait strictement rien, parce que color vit dans un slot et non au niveau du dessus. Écrivez le slot :

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

Et n'affectez jamais directement dans $data. Cela a l'air d'avoir marché, et le changement est jeté.

Des réglages dans l'inspecteur

defineProps est ce qui rend un script digne d'être réutilisé : le même script sur dix objets, configuré différemment sur chacun, sans le moindre copier-coller.

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' },
});
TypeL'éditeur afficheVotre script reçoit
numberun champ ou un curseurun nombre
stringun champ texteune chaîne
booleanun interrupteurun booléen
colorun sélecteur de couleurune chaîne de couleur
selectune liste déroulanteune de vos options
entityun sélecteur d'objetl'objet lui-même, prêt à l'emploi
sceneun sélecteur de scènel'objet scène
resourceun sélecteur de ressourceune référence à transmettre
arrayune listeun tableau
groupun bloc titréun objet imbriqué

Bon à savoir : label et help pour le panneau, min et max pour de vraies limites, softMax pour l'endroit où s'arrête un curseur sans interdire une valeur saisie plus grande, showWhen pour ne révéler un champ que si un autre a une valeur donnée, et group pour garder une longue liste rangée.

defineProps doit être écrit littéralement

L'éditeur lit vos réglages sans exécuter le script : il doit donc les voir directement, pas construits à partir de variables ni renvoyés par une fonction.

Les valeurs sont relues à chaque fois, donc props.speed reflète toujours ce qui se trouve dans l'inspecteur à cet instant.

Importer des choses

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

Vous pouvez importer les classes de composants du moteur, et d'autres ressources par leur chemin : un autre script vous donne ses exports, un patch vous donne son module compilé, et n'importe quoi d'autre vous donne une référence que vous pouvez passer à ctx.spawn ou ctx.audio.play.

Renommez ou déplacez une ressource et ces imports se mettent à jour tout seuls.

Ce que le bac à sable donne — et ne donne pas

Les scripts tournent isolés de la page, ce qui empêche un script lourd de bloquer le rendu. Ne sont donc pas disponibles :

  • window, document, le DOM ;
  • fetch, localStorage, tout accès réseau ;
  • toute bibliothèque de rendu — vous changez la scène par les composants, pas en dessinant ;
  • les minuteries du navigateur — utilisez ctx.tick.

Ce que vous utilisez à la place : les cartes d'interface pour l'interface, ctx.audio pour le son, ctx.store et les globals pour retenir des choses.

Quand votre script démarre et s'arrête

Une instance est créée quand son objet est vivant et à l'écran — rien de désactivé au-dessus de lui, sa scène active — et détruite quand ce n'est plus le cas.

on-launch se déclenche dès sa création : vous ne le manquez jamais. La fonction que vous renvoyez depuis init est votre nettoyage : se désabonner, arrêter les sons, remettre l'état à zéro.

Les scripts ne tournent pas dans l'éditeur, et il n'y a pas de rechargement à chaud

L'éditeur dessine votre scène mais n'exécute pas la logique. Modifiez, puis ouvrez Preview. Une expérience en cours reprend un script modifié à son prochain redémarrage.


Suite : Où vit un script