Плагины редактора — registerEditorWindow / registerEditorTool
Расширяй редактор сцен le.codes из кода проекта: оверлейные панели и инструменты вьюпорта,
автоматизирующие рутинную сборку сцен (рассыпание пропов, связывание узлов, измерения).
Регистрации живут в файлах *.editor.ts — они компилируются и выполняются только в бандлах
редактора (файлы сцен), поэтому плагины стоят
ноль байт в поставке.
// scatter.editor.ts — только бандлы редактора
const state = { model: "", count: 5 }
registerEditorWindow("Scatter", (ui, editor) => {
state.model = ui.asset("model")
state.count = ui.slider("count", { min: 1, max: 20, step: 1, value: 5 })
ui.toolButton("Paint", "scatter")
ui.info(`${editor.nodes().length} nodes in the scene`)
})
registerEditorTool("scatter", {
cursor: "copy",
onViewportClick(hit, editor) {
editor.transact(() => { // вся покраска = ОДИН шаг undo
for (let i = 0; i < state.count; i++) {
editor.addNode(editor.uniqueName("prop"), {
model: state.model,
position: [ hit.point[0] + Math.random(), hit.point[1], hit.point[2] + Math.random() ],
eulerAngles: [ 0, Math.random() * 360, 0 ],
})
}
})
},
})Плагины пишут в документ, никогда — в живое состояние
Каждая запись через editor попадает в файл сцены через обычный путь коммита редактора —
поэтому действие плагина отменяемо (один стек doc-патчей для людей и плагинов), выглядит обычным
git-диффом и применяется к запущенному вьюпорту на самом дешёвом живом уровне. Багованный плагин
в худшем случае запишет плохие данные; испортить живое состояние движка он не может.
Окна
registerEditorWindow(title, (ui, editor) => …) добавляет сворачиваемую панель, пришвартованную
поверх вьюпорта (рейка инспектора остаётся привязанной к выделению — окна глобальны для
редактора). Окно, эмитящее ui.toolButton для инструмента, — панель настроек этого
инструмента: активация инструмента раскрывает и подсвечивает её, так что настройки лежат ровно
там, где инструмент используется. Функция рендера — immediate-mode, тот же протокол
InspectorUI, что у своих карточек
инспектора: она перезапускается при каждом
взаимодействии (и при каждом изменении документа) и описывает панель вызовами виджетов. Все ключи
полей окна — временное состояние редактора (у окон нет записи в документе): размеры кистей,
выбранные ассеты; они переживают перерендеры, но никогда не пишутся в файл. К словарю добавляются
два оконных виджета:
| Виджет | Что делает |
|---|---|
ui.asset(key) |
Дропдаун ассетов проекта (GLB-пути проекта). Возвращает путь, "" = ничего. |
ui.toolButton(label, tool) |
Переключает именованный инструмент вьюпорта — рисуется нажатым, пока тот активен. |
Чтобы поделиться значениями виджетов с инструментом, копируй их в состояние модуля в рендере (пример выше) — immediate-mode возвращает текущее значение на каждом прогоне.
Инструменты вьюпорта
registerEditorTool(name, hooks) добавляет запись в тулбар рядом с move/rotate/scale (также
активируется через ui.toolButton, деактивируется Esc или W/E/R). Пока инструмент активен, гизмо
трансформа скрыто, клики выделения приостановлены, и каждый клик по вьюпорту вызывает
onViewportClick(hit, editor):
type EditorRayHit = {
point: [number, number, number] // точка попадания в мировых координатах
normal: [number, number, number]
node: string | null // узел файла сцены, в который попали; null = плоскость земли
}На сборках с физикой попадание берётся из точного рейкаста по мешам сцены; иначе (и при промахах)
идёт фолбэк на плоскость земли (y = 0). hooks.cursor задаёт CSS-курсор вьюпорта (по умолчанию
"crosshair"); hooks.icon задаёт глиф кнопки в тулбаре (один символ / эмодзи — инструменты без
него делят общую «волшебную палочку», так что задавай его всякий раз, когда редактор регистрирует
два и больше инструментов). Бросающий хук логируется и пропускается — уронить редактор он не
может.
API editor
Передаётся рендерам окон и хукам инструментов:
| Член | Что делает |
|---|---|
selection / select(name) |
Имя выделенного узла (чтение / смена). |
nodes() |
Узлы документа: { name, kind }[] (mesh / model / light / group). |
uniqueName(base) |
Первое незанятое имя base, base2, …. |
addNode(name, def) |
Добавить узел из простых данных дефа (грамматика файла сцены; строковое значение model — путь ассета). |
setProp(name, key, value) |
Записать один проп дефа — трансформы применяются вживую, остальное патчится. |
removeNode(name) / duplicate(name) |
Удалить / дублировать (возвращает имя копии). |
raycast(x, y) |
Тот же рейкаст вьюпорта, который получают клики инструментов. |
transact(fn) |
Сгруппировать все правки документа внутри fn в ОДИН шаг undo. |
Дефы с model-ассетом перезапускают компиляцию сцены (бандл должен завендорить URL ассета); дефы
mesh/light/group применяются живыми патчами без компиляции.
Реестр __editorPlugins, который питают регистрации, — внутренний: его читает обвязка редактора
сцен; никогда не трогай его напрямую.