Saltar al contenido principal

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:

HookTe 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, Checkbox y Select. En el visor: Overlay, Gizmo, Group, Box, Sphere, Line, Handle y Control. Pon when="selected" en un Gizmo o en un Overlay para mostrarlo solo en el objeto seleccionado.
  • Los manejadores de eventos se escriben como siempre. onClick funciona 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 desdeResultado
+ new component, en el grupo Extensionsse añade al objeto que ya está seleccionado
La barra de herramientas, bajo +, en el grupo Pluginsse 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​

Se ejecuta sin que nadie haga clic

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.