Перейти к основному содержимому

Общие пакеты

Эти пакеты можно импортировать в свой код. Здесь — что где можно использовать и что даёт каждый пакет.

Сущности — это объекты

В коде объекты, которые вы видите в редакторе, называются сущностями (entities). Это одно и то же.

Какие пакеты можно импортировать, зависит от того, где выполняется код:

ПакетЧто даётВ скриптеВ плагинеВ расширении компонента
@was/ecsсущности, компоненты, мир✓✓✓
@was/engineклассы компонентов✓✓✓
@was/signalsреактивные значения—✓✓
@was/svdtсхемы и проверка данных✓✓—
@was/utilsнебольшие вспомогательные функции✓✓—
@was/uiбиблиотека компонентов самого редактора—✓✓
@was/iconsнабор иконок редактора—✓✓
reactReact 19—✓✓
В скрипте используйте ctx.effect

Скрипт не может импортировать @was/signals напрямую. Используйте вместо него ctx.effect. Он работает так же и останавливается, когда пропадает его объект, так что ничего не остаётся работать впустую.

@was/signals: реактивные значения​

Сигнал — это значение, которое знает, кто его читает. Когда оно меняется, обновляется только то, что от него зависит.

import { signal, computed, effect, batch, untracked } from '@was/signals';

const score = signal(0);
const doubled = computed(() => score.value * 2);

const stop = effect(() => {
render(score.value); // выполняется снова, только когда меняется score
});

batch(() => {
score.value += 1;
score.value += 1; // эффекты выполнятся один раз, а не два
});

stop();
ФункцияЧто делает
signal(v)значение, за которым можно следить; читайте и записывайте .value
computed(fn)производное значение; пересчитывается, только когда меняется то, что оно читает
effect(fn)выполняется сразу и снова каждый раз, когда меняется то, что он прочитал; возвращает функцию остановки
batch(fn)объединяет записи, чтобы наблюдатели сработали один раз в конце
untracked(fn)читает значение, не создавая зависимости от него
ref(obj)делает реактивным весь объект, вплоть до каждого вложенного значения
snapshot(obj)обычная нереактивная копия
raw(obj)исходный объект без отслеживания
readonly(obj)представление, в которое нельзя записывать

Для панелей на React есть сопутствующий пакет с useSignal, useComputed, useSignalEffect и useLiveSignal. С ним компонент перерисовывается от сигнала без лишней обвязки.

@was/ui: компоненты редактора​

Собирайте панели плагинов и расширения компонентов из тех же компонентов, что использует редактор. Тогда ваши панели выглядят как часть редактора, а не как встроенная веб-страница. В библиотеке есть такие компоненты:

Accordion · Avatar · Button · Card · Checkbox · Chip · CloseButton
DimensionInput · Draggable · Dropdown · Flag · Icon · IconButton · Input
Menu · Modal · Outside · Panel · Popover · Portal · Render · Scroll
Search · Section · Segmented · Select · Skeleton · Switcher · Tabs
Toast · Toolbar · Tooltip

Для иконок используйте @was/icons.

Таблица стилей собрана заранее

В панелях работают только те служебные классы, которые поставляются с библиотекой. Произвольных значений вроде text-[13px] там нет. Для размеров вне шкалы используйте встроенный style.

@was/ecs: сущности и компоненты​

В этом пакете есть Entity, Component и Space. С ним плагин читает и меняет сцену через полный API, а не через отдельный урезанный.

Space: мир​

ВызовВозвращает
getEntity(id)одну сущность или ничего
hasEntity(id)есть ли такая сущность
getEntities()все сущности
queryEntities(A, B, …)все сущности, у которых есть все эти компоненты
makeQueryEntities(A, B, …)тот же запрос, собранный один раз для повторного использования
createEntity(id?, components?)новую сущность, уже добавленную в мир
addEntity(…e) · removeEntity(…e)добавляет сущность в мир или убирает её
getSystem(S) · hasSystem(S)систему или то, есть ли она
execute()выполняет один кадр
Ищите сущности через queryEntities

Так запрос «всё, у чего есть свет и трансформация» выполняется быстро. getEntities() отдаёт весь мир, и фильтровать его придётся самому.

Entity: один объект в мире​

ВызовЧто делает
getComponent(Type)возвращает один компонент или null
getComponents(A, B)возвращает несколько сразу, в том же порядке
hasComponent(A, B)проверяет, есть ли все эти компоненты
addComponent(…c)добавляет компоненты; тип, который уже есть, пропускается
removeComponent(…c)убирает компоненты по классу или по экземпляру
component(fn)выполняет fn для каждого компонента — и нынешнего, и будущего
clone() · clean()копирует сущность или убирает все её компоненты
.id · .componentsеё id и её компоненты по типам

Component: данные​

ВызовЧто делает
.x или get('x')читает поле; внутри эффекта ещё и подписывается на него
$dataвесь компонент как обычный объект
$rawDataхранимый объект без подписки; только для чтения
update({ … })записывает данные. Записывать можно только так.
updateAt(path, value)записывает глубоко внутри большого компонента, не проверяя его заново целиком
version(field?)счётчик, который растёт при каждом изменении, — так можно зависеть от поля, не читая его
reset(data?)возвращает значения по умолчанию
clone()делает копию
Для больших списков используйте version()

Если прочитать большое значение, вы подписываетесь на каждый элемент внутри него. Лучше зависьте от его счётчика версий: вы узнаете, что оно изменилось, не следя за каждым элементом. Так панель остаётся быстрой со списком из двадцати тысяч элементов.

@was/engine: классы компонентов​

Каждый тип компонента — это класс: TransformComponent, MaterialComponent, RigidBodyComponent и остальные. Импортируйте класс и передайте его в getComponent, hasComponent или query.

Полный список со всеми полями — в справочнике компонентов.

@was/svdt: схемы​

С этим пакетом можно описать структуру своих данных и проверять их.

import { s, compile } from '@was/svdt';

const schema = s.object({ speed: s.f64(1), name: s.string('') });
const codec = compile(schema); // компилируйте один раз, там же, где объявляете, а не при каждом вызове
const value = codec.parse(input);

Как описать структуру​

  • Числа: s.f32 s.f64 s.i8 s.u8 s.i16 s.u16 s.i32 s.u32, у каждого есть значение по умолчанию.
  • Простые значения: s.bool, s.string, s.color, s.literal, s.enum, s.unknown, s.ref.
  • Векторы: s.vec2, s.vec3, s.mat4.
  • Составные: s.object, s.variant, s.array, s.record, s.union, s.preprocess, s.lazy.

К любому из них можно добавить цепочкой: .default(v), .optional(), .nullable(), .min(n), .max(n), .int().

Как пользоваться скомпилированным кодеком​

ВызовЧто делает
parse(input)проверяет и нормализует; при плохих данных бросает ошибку
safeParse(input)то же самое, но возвращает успех или ошибку, а не бросает её
parseAt(path, v)проверяет одно поле, не трогая остальные
equals(a, b)глубокое сравнение
diff(a, b)возвращает, что изменилось
apply(target, p)применяет разницу
invert(p)обращает разницу
pack · unpackпереводит в компактный двоичный вид и обратно

Чтобы изучить саму схему, а не данные, используйте introspect, keys и requiredKeys.

@was/utils​

Небольшие вспомогательные функции: pick, omit, assign, keys, values, entries, capitalize, basename, extname, dispose (объединяет функции очистки в одну) и функции для MIME-типов, чтобы проверять типы файлов.


Дальше: Глоссарий — термины, которые встречаются в документации.