Vos propres composants
Le second type de plugin n'ouvre aucun panneau. Il s'attache à un objet comme composant et dessine son interface dans l'inspecteur de l'éditeur, à côté de Transform et de Material.
C'est la bonne forme quand votre outil parle d'un objet en particulier : des notes sur un objet, un contrôle sur mesure, un générateur qui appartient à la chose qu'il génère.
En déclarer un
Déclarez-le dans components ; panels peut rester vide :
{
"id": "entity-notes",
"entry": "main.tsx",
"panels": [],
"components": [{ "id": "notes", "title": "Notes", "entry": "main.tsx" }]
}
En écrire un
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(); // suivre les données de scène lues au rendu
const props = useProps(); // les valeurs du formulaire de l'inspecteur
const entity = useEntity(); // l'objet auquel ceci est attaché
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>
);
}
Quatre hooks font le travail :
| Hook | Vous donne |
|---|---|
useEntity() | l'objet auquel votre composant est attaché |
useProps() | les valeurs du formulaire déclaré avec defineProps |
useSignals() | un nouveau rendu quand les données de scène lues au rendu changent |
useSyncedState() | un état stocké sur l'objet et synchronisé comme tout composant |
defineProps utilise les mêmes types de champs que les propriétés de script — nombre, chaîne,
booléen, couleur, liste, objet, ressource — si bien que votre composant obtient gratuitement un
vrai formulaire d'inspecteur.
Comment cela s'exécute
Toutes les extensions de composant de tous les plugins tournent dans un unique worker partagé, avec
React 19 dedans. Les hooks, useEffect, le contexte et Suspense fonctionnent exactement comme
prévu.
Ce que produit le worker n'est pas du DOM mais une description de celui-ci, que l'éditeur dessine avec ses propres composants. C'est ce qui fait qu'une extension a l'air native plutôt que d'une page embarquée.
Deux conséquences de cette frontière :
- Le jeu d'éléments est une liste fixe. Vous construisez à partir des composants fournis, pas de HTML arbitraire.
- Les gestionnaires ne franchissent pas la frontière. On dit seulement à l'éditeur qu'un
gestionnaire existe ; un clic revient sous forme de message et appelle votre fonction dans le
worker. Vous écrivez
onClicknormalement — cela ne compte que si vous comptiez faire circuler un événement DOM.
Comment on l'ajoute
Votre extension apparaît dans + New component, dans un groupe Extensions — elle n'est pas traitée comme plus spéciale qu'un composant intégré.
Elle apparaît aussi dans la barre d'outils sous +, regroupée sous un titre Plugins pour qu'on voie d'où vient un bouton inconnu. Les deux points d'entrée se comportent différemment, et c'est voulu :
| Ajouté depuis | Résultat |
|---|---|
| + New component | s'attache à l'objet déjà sélectionné |
| La barre d'outils | crée un nouvel objet avec votre composant et le sélectionne |
Un dossier connecté localement apparaît aussi dans cette liste s'il a un panneau — sinon le seul moyen de l'atteindre serait d'ouvrir le panneau Plugins à chaque fois. Un dossier ne contenant qu'une surcouche n'apparaît pas : il n'y aurait rien à presser.
Confiance
Elle se monte dès que quelqu'un ouvre un projet qui la contient, sans le moindre clic. C'est ce qui la place derrière le consentement renforcé décrit dans confiance et vérification.
Suite : Raccourcis clavier