Aller au contenu principal

Composants personnalisés

Un plugin peut ajouter son propre composant aux objets. Son interface s'affiche dans l'inspecteur, à côté de Transform et Material.

C'est utile quand votre outil concerne un seul objet : des notes sur un objet, un contrôle sur mesure, ou un générateur qui reste attaché à ce qu'il crée. Un plugin de ce type n'a pas besoin de panneau.

Déclarer le composant​

Listez-le sous components dans le manifeste. panels peut être vide :

{
"id": "entity-notes",
"entry": "main.tsx",
"panels": [],
"components": [{ "id": "notes", "title": "Notes", "entry": "main.tsx" }]
}

Écrire le composant​

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(); // nouveau rendu quand les données de scène lues ici changent
const props = useProps(); // les valeurs du formulaire de l'inspecteur
const entity = useEntity(); // l'objet auquel le composant 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>
);
}

Ces hooks font le travail :

HookCe qu'il vous donne
useEntity()l'objet auquel votre composant est attaché
useWorld()la scène dans laquelle se trouve cet objet
useProps()les valeurs du formulaire déclaré avec defineProps
useSignals()un nouveau rendu quand les données de scène lues pendant le rendu changent
useSyncedState()un état stocké sur l'objet et synchronisé comme n'importe quel composant
useSelected()si l'objet est sélectionné dans l'éditeur
useViewportPointer()les événements de souris dans la zone d'affichage, avec l'objet et le point sous le pointeur

defineProps utilise les mêmes types de champs que les propriétés de script : nombre, chaîne, booléen, couleur, liste de choix, entité, scène, espace, ressource, enregistrement (record), tableau et groupe. Votre composant obtient ainsi un vrai formulaire dans l'inspecteur, sans travail supplémentaire.

Modifier les valeurs depuis votre composant​

props.$set(patch) modifie les champs de votre propre composant, comme le fait l'inspecteur : la modification est synchronisée chez tout le monde et peut être annulée. Elle est vérifiée par rapport à defineProps : les nombres restent entre leurs min et max, et un champ que vous n'avez pas déclaré lève une erreur. Utilisez-la pour faire glisser une valeur dans la zone d'affichage :

<Handle axis={[0, 1, 0]} onDrag={(p) => props.$set({ height: p[1] })} />

Les noms de champs qui commencent par $ sont réservés à cet usage : defineProps les refuse donc.

Déclarer l'état​

defineState déclare l'état du composant avec ses valeurs par défaut, de la même façon que defineProps déclare le formulaire :

const useScore = defineState({ points: { type: 'number', default: 0 } });

const [score, setScore] = useScore(); // à l'intérieur du composant

Les scripts lisent alors cet état avec ses types, et avec les mêmes valeurs par défaut.

Le traduire​

i18n() ou useEditorLocale() de @was/extension vous donnent t(key, vars) sur les fichiers de langue de votre plugin, dans la langue de l'éditeur.

Comment il s'exécute​

Votre composant est du vrai React 19. Les hooks, useEffect, le contexte et Suspense fonctionnent comme d'habitude.

L'éditeur dessine votre composant avec ses propres éléments. C'est pour cela qu'il a l'air de faire partie de l'éditeur.

Deux conséquences :

  • Vous construisez uniquement avec les éléments fournis. Le HTML libre n'est pas disponible. Dans l'inspecteur : Column, Row, Text, Button, Divider, InputText, InputNumber, InputColor, Slider, Checkbox et Select. Dans la zone d'affichage : Overlay, Gizmo, Group, Box, Sphere, Line, Handle et Control. Donnez when="selected" à un Gizmo ou à un Overlay pour ne l'afficher que sur l'objet sélectionné.
  • Les gestionnaires d'événements s'écrivent comme d'habitude. onClick fonctionne normalement. La seule chose impossible : faire circuler un événement DOM.

Comment l'ajouter à un objet​

Votre composant peut être ajouté de deux façons :

Ajouté depuisRésultat
+ new component, dans le groupe Extensionsil est ajouté à l'objet déjà sélectionné
La barre d'outils, sous +, dans le groupe Pluginsun nouvel objet est créé avec votre composant, puis sélectionné

L'intitulé Plugins dans la barre d'outils indique d'où vient un bouton inconnu.

Un dossier de plugin connecté depuis votre ordinateur apparaît aussi dans cette liste de la barre d'outils s'il a un panneau : vous pouvez donc l'ouvrir sans passer à chaque fois par le panneau Plugins. Un dossier qui ne contient qu'une surcouche n'y apparaît pas, puisqu'il n'y a rien à ouvrir.

L'utiliser dans les scripts et dans le projet publié​

L'interface du composant ne fonctionne que dans l'éditeur. Ses valeurs sont enregistrées sur l'objet, si bien que le projet peut s'en servir quand il est joué :

  • les scripts lisent et modifient le composant avec ctx.plugin('<plugin id>/<component id>') → L'API ctx ;
  • le code de votre plugin pour le projet publié fait de même → Code d'exécution.

Confiance​

Il démarre sans aucun clic

Un composant personnalisé démarre dès que quelqu'un ouvre un projet qui le contient. C'est pour cela qu'il passe par le contrôle plus strict décrit dans Confiance et vérification.


Suite : Code d'exécution — faites fonctionner votre plugin dans le projet publié.