Saltar al contenido principal

Tu primer script

Un script es un archivo de TypeScript que adjuntas a un objeto. Corre en el reproductor, una vez por cada objeto que lo use.

Aquí tienes uno completo: tocas el objeto y empieza a girar.

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 () => {
// opcional: limpiar cuando este objeto desaparezca
};
});

Aquí pasan cuatro cosas:

initrecibe ctx, tu asa sobre la escena en marcha. Todo empieza aquí
definePropsdeclara los ajustes que el editor muestra en el inspector, rellenados por objeto
ctx.onse suscribe a un disparador: los mismos que usan eventos y patches
ctx.tickcorre en cada fotograma, con dt en segundos

init y defineProps están siempre disponibles: nunca los importas.

Leer y escribir objetos

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

transform.position; // leer un valor
transform.$data; // leer todo como objeto llano
transform.update({}); // escribir

Dos trampas, y son las dos que pillan a todo el mundo:

Escribir un valor anidado lo reemplaza entero

update({ position: { y: 2 } }) también pone x y z a cero: le has entregado una posición completamente nueva con solo y rellenado.

Expande lo que quieras conservar:

transform.update({ position: { ...transform.$data.position, y: 2 } });
Un material es una lista de slots

material.update({ color }) no hace absolutamente nada, porque color vive dentro de un slot y no en el nivel superior. Escribe el slot:

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

Y nunca asignes directamente dentro de $data. Parece que funcionó, y el cambio se descarta.

Ajustes en el inspector

defineProps es lo que hace que merezca la pena reutilizar un script: el mismo script en diez objetos, configurado distinto en cada uno, sin copiar y pegar.

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' },
});
TipoEl editor muestraTu script recibe
numberun campo o un deslizadorun número
stringun campo de textouna cadena
booleanun interruptorun booleano
colorun selector de coloruna cadena de color
selectun desplegableuna de tus opciones
entityun selector de objetoel objeto en sí, listo para usar
sceneun selector de escenael objeto escena
resourceun selector de recursouna referencia que puedes pasar
arrayuna listaun array
groupun bloque con títuloun objeto anidado

Conviene saber: label y help para el panel, min y max para límites de verdad, softMax para dónde termina un deslizador sin prohibir valores mayores escritos a mano, showWhen para revelar un campo solo cuando otro tiene cierto valor, y group para mantener ordenada una lista larga.

defineProps hay que escribirlo literalmente

El editor lee tus ajustes sin ejecutar el script, así que necesita verlos directamente: ni construidos a partir de variables ni devueltos por una función.

Los valores se leen frescos cada vez, así que props.speed siempre refleja lo que hay en el inspector en ese momento.

Importar cosas

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

Puedes importar las clases de componente del motor y otros recursos por su ruta: otro script te da sus exports, un patch te da su módulo compilado, y cualquier otra cosa te da una referencia que puedes pasar a ctx.spawn o ctx.audio.play.

Renombra o mueve un recurso y estos imports se actualizan solos.

Qué te da el sandbox, y qué no

Los scripts corren aislados de la página, lo que impide que un script pesado atasque el renderizado. Así que no están disponibles:

  • window, document, el DOM;
  • fetch, localStorage, ningún tipo de acceso a red;
  • ninguna biblioteca de renderizado: cambias la escena a través de componentes, no dibujando;
  • los temporizadores del navegador: usa ctx.tick.

Qué usas en su lugar: tarjetas de interfaz para la interfaz, ctx.audio para el sonido, ctx.store y los globals para guardar cosas.

Cuándo arranca y se detiene tu script

Una instancia se crea cuando su objeto está vivo y en pantalla —nada desactivado por encima, su escena activa— y se destruye cuando eso deja de ser cierto.

on-launch se dispara en cuanto se crea, así que nunca te lo pierdes. La función que devuelves desde init es tu limpieza: desuscribirte, parar sonidos, vaciar el estado.

Los scripts no corren en el editor, y no hay recarga en caliente

El editor dibuja tu escena pero no ejecuta la lógica. Edita y luego abre Preview. Una experiencia en marcha recoge un script editado en su siguiente reinicio.


Siguiente: Dónde vive un script