Tus propios componentes
El segundo tipo de plugin no abre ningún panel. Se adjunta a un objeto como componente y dibuja su interfaz dentro del propio inspector del editor, junto a Transform y Material.
Es la forma adecuada cuando tu herramienta trata de un objeto en concreto: notas sobre un objeto, un control a medida, un generador que pertenece a aquello que genera.
Declararlo
Decláralo en components; panels puede quedar vacío:
{
"id": "entity-notes",
"entry": "main.tsx",
"panels": [],
"components": [{ "id": "notes", "title": "Notes", "entry": "main.tsx" }]
}
Escribirlo
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(); // seguir los datos de escena leídos al renderizar
const props = useProps(); // valores del formulario del inspector
const entity = useEntity(); // el objeto al que esto está adjunto
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>
);
}
El trabajo lo hacen cuatro hooks:
| Hook | Te da |
|---|---|
useEntity() | el objeto al que está adjunto tu componente |
useProps() | los valores del formulario que declaraste con defineProps |
useSignals() | un nuevo render cuando cambian los datos de escena que leíste |
useSyncedState() | estado guardado en el objeto y sincronizado como cualquier componente |
defineProps usa los mismos tipos de campo que las propiedades de script — número, cadena,
booleano, color, selección, objeto, recurso — así que tu componente obtiene un formulario de
inspector en condiciones sin esfuerzo.
Cómo se ejecuta
Todas las extensiones de componente de todos los plugins corren en un único worker compartido, con
React 19 dentro. Los hooks, useEffect, el contexto y Suspense funcionan exactamente como esperas.
Lo que produce el worker no es DOM, sino una descripción de él, que el editor dibuja con sus propios componentes. Eso es lo que hace que una extensión parezca nativa y no una página incrustada.
Dos consecuencias de esa frontera:
- El conjunto de elementos es una lista fija. Construyes con los componentes provistos, no con HTML arbitrario.
- Los manejadores no cruzan la frontera. Al editor solo se le dice que existe un manejador; un
clic vuelve como mensaje y llama a tu función dentro del worker. Escribes
onClickcon normalidad: esto solo importa si esperabas pasear un evento del DOM.
Cómo lo añade la gente
Tu extensión aparece en + New component, dentro de un grupo Extensions: no se le trata como nada más especial que a un componente integrado.
También aparece en la barra de herramientas bajo +, agrupada bajo el encabezado Plugins para que se vea de dónde salió un botón desconocido. Los dos puntos de entrada se comportan distinto, y es a propósito:
| Añadido desde | Resultado |
|---|---|
| + New component | se adjunta al objeto que ya tenías seleccionado |
| La barra de herramientas | crea un objeto nuevo con tu componente y lo selecciona |
Una carpeta conectada localmente también sale en esa lista si tiene un panel; si no, la única forma de llegar a ella sería abrir el panel de Plugins cada vez. Una carpeta que solo contiene una superposición no aparece: no habría nada que pulsar.
Confianza
Se monta cada vez que alguien abre un proyecto que la contiene, sin ningún clic de por medio. Eso es lo que la deja detrás del consentimiento más estricto descrito en confianza y verificación.
Siguiente: Atajos de teclado