Componentes personalizados
Un plugin puede añadir su propio componente a los objetos. Su interfaz aparece en el inspector, junto a Transform y Material.
Úsalo cuando tu herramienta pertenezca a un solo objeto: notas sobre un objeto, un control propio o un generador que se queda con lo que crea. Un plugin así no necesita panel.
Declara el componente
Inclúyelo en components dentro del manifiesto. panels puede quedar vacío:
{
"id": "entity-notes",
"entry": "main.tsx",
"panels": [],
"components": [{ "id": "notes", "title": "Notes", "entry": "main.tsx" }]
}
Escribe el componente
import {
Button,
Column,
Text,
defineProps,
useEntity,
useSignals,
useSyncedState,
} from '@was/extension';
import { TransformComponent } from '@was/engine';
const useProps = defineProps({ step: { type: 'number', default: 0.5 } });
export default function Notes() {
useSignals(); // vuelve a renderizar si cambian los datos de escena leídos aquí
const props = useProps(); // valores del formulario del inspector
const entity = useEntity(); // el objeto al que está unido
const [note, setNote] = useSyncedState('note', '');
return (
<Column gap={2}>
<Text muted>{note}</Text>
<Button
onClick={() =>
setNote(`y=${entity.getComponent(TransformComponent)?.get('position').y}`)
}
>
Remember
</Button>
</Column>
);
}
Estos hooks hacen el trabajo:
| Hook | Te da |
|---|---|
useEntity() | el objeto al que está unido tu componente |
useWorld() | la escena en la que está ese objeto |
useProps() | los valores del formulario que declaraste con defineProps |
useSignals() | un nuevo renderizado cuando cambian los datos de escena que lees al renderizar |
useSyncedState() | estado guardado en el objeto y sincronizado como cualquier otro componente |
useSelected() | si el objeto está seleccionado en el editor |
useViewportPointer() | los eventos del ratón en el visor, con el objeto y el punto que hay bajo el puntero |
defineProps usa los mismos tipos de campo que las propiedades de los scripts: number, string,
boolean, colour, select, entity, scene, space, resource, record, array y group. Así tu componente
tiene un formulario en condiciones en el inspector sin trabajo extra.
Cambia los valores desde tu componente
props.$set(patch) cambia los campos de tu propio componente igual que lo hace el inspector: el
cambio se sincroniza con todos y se puede deshacer. Se comprueba con defineProps: los números
se quedan entre su min y su max, y un campo que no declaraste lanza un error. Úsalo para
arrastrar un valor en el visor:
<Handle axis={[0, 1, 0]} onDrag={(p) => props.$set({ height: p[1] })} />
Los nombres de campo que empiezan por $ están reservados para esto, así que defineProps los
rechaza.
Declara el estado
defineState declara el estado del componente con sus valores por defecto, igual que
defineProps declara el formulario:
const useScore = defineState({ points: { type: 'number', default: 0 } });
const [score, setScore] = useScore(); // dentro del componente
Así los scripts leen este estado con sus tipos y con los mismos valores por defecto.
Tradúcelo
i18n() o useEditorLocale() de @was/extension te dan t(key, vars) sobre los
archivos de idioma de tu plugin, en el idioma
del editor.
Cómo se ejecuta
Tu componente es React 19 de verdad. Los hooks, useEffect, el contexto y Suspense funcionan
como siempre.
El editor dibuja tu componente con sus propios elementos. Por eso parece parte del editor.
Esto tiene dos consecuencias:
- Solo puedes construir con los elementos que se te dan. No hay HTML arbitrario. En el
inspector:
Column,Row,Text,Button,Divider,InputText,InputNumber,InputColor,Slider,CheckboxySelect. En el visor:Overlay,Gizmo,Group,Box,Sphere,Line,HandleyControl. Ponwhen="selected"en unGizmoo en unOverlaypara mostrarlo solo en el objeto seleccionado. - Los manejadores de eventos se escriben como siempre.
onClickfunciona con normalidad. Lo único que no puedes hacer es pasar un evento del DOM de un lado a otro.
Cómo lo añade la gente
Tu componente se puede añadir de dos formas:
| Se añade desde | Resultado |
|---|---|
| + new component, en el grupo Extensions | se añade al objeto que ya está seleccionado |
| La barra de herramientas, bajo +, en el grupo Plugins | se crea un objeto nuevo con tu componente y queda seleccionado |
El título Plugins en la barra de herramientas le indica a la gente de dónde ha salido un botón que no conoce.
Una carpeta de plugin conectada desde tu ordenador también aparece en esa lista de la barra de herramientas si tiene un panel, así puedes abrirla sin pasar cada vez por el panel Plugins. Una carpeta que solo tiene una capa sobre el visor no aparece, porque no hay nada que abrir.
Úsalo en scripts y en el proyecto publicado
La interfaz del componente solo funciona en el editor. Sus valores se guardan en el objeto, así que el proyecto puede usarlos cuando se reproduce:
- los scripts leen y cambian el componente con
ctx.plugin('<plugin id>/<component id>')→ La API de ctx; - el propio código de tu plugin para el proyecto publicado hace lo mismo → Código de ejecución.
Confianza
Un componente personalizado se ejecuta cada vez que alguien abre un proyecto que lo contiene. Por eso pasa por la comprobación más estricta que se describe en Confianza y verificación.
Siguiente: Código de ejecución: haz que tu plugin funcione en el proyecto publicado.