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 :
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 :
init | reçoit ctx, votre poignée sur la scène en cours. Tout commence ici |
defineProps | déclare les réglages que l'éditeur affiche dans l'inspecteur, remplis par objet |
ctx.on | s'abonne à un déclencheur — les mêmes que ceux des événements et des patches |
ctx.tick | s'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 :
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 } });
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' },
});
| Type | L'éditeur affiche | Votre script reçoit |
|---|---|---|
number | un champ ou un curseur | un nombre |
string | un champ texte | une chaîne |
boolean | un interrupteur | un booléen |
color | un sélecteur de couleur | une chaîne de couleur |
select | une liste déroulante | une de vos options |
entity | un sélecteur d'objet | l'objet lui-même, prêt à l'emploi |
scene | un sélecteur de scène | l'objet scène |
resource | un sélecteur de ressource | une référence à transmettre |
array | une liste | un tableau |
group | un 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éralementL'é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.
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