Почему этот движок
Не список фич, а горстка решений, которые меняют то, что вы можете построить, и почему они важны на практике.
Одна модель везде
Редактор, веб-плеер, приложение на телефоне и сервер работают с одной и той же сценой: те же объекты, те же компоненты, то же поведение.
Нет шага экспорта, нет отдельного «формата рантайма», нет второй реализации, которая разошлась бы с первой. То, что вы расставили в редакторе, — это то, что работает, а баг, который вы видите в плеере, воспроизводится везде.
Практическое следствие скучное и ценное: при публикации вещи не меняют смысл.
Графы становятся кодом, а не интерпретатором
Визуальный граф в большинстве инструментов — это данные, по которым что-то ходит во время работы, нода за нодой, каждый кадр. Здесь он компилируется в обычный скрипт, один раз, и работает именно этот скрипт.
| Скорость | граф стоит столько же, сколько написанный руками код, потому что он и есть код |
| Прозрачность | откройте панель кода и прочитайте ровно то, во что превратился ваш граф |
| Компонуемость | граф может вызвать скрипт, а скрипт — граф, потому что под капотом это одно и то же |
Так что нет потолка, на котором придётся бросить визуальный инструмент и переписать всё кодом. Вы просто добавляете скрипт рядом с графом, который уже есть.
Материалы — графы до самого низа
Встроенные типы материалов — не фиксированный набор с прикрученным нодовым редактором. Это точки внутри той же системы, которой пользуется пользовательский граф, — поэтому граф-материал не медленнее, не второсортен и не ограничен вебом: один и тот же граф компилируется и для браузера, и для нативного рендерера на телефоне.
И то, чем вы не пользуетесь, не стоит ничего. Физические дополнения, оставленные на значениях по умолчанию, сворачиваются при компиляции, так что материал с возможностью стекла ровно так же дёшев, как материал без неё.
Это работает как электронная таблица
Вот решение, на котором держится всё остальное, и проще всего объяснить его по аналогии.
В таблице вы меняете одну ячейку, и пересчитываются только формулы, которые эту ячейку используют. Никто не пересчитывает весь лист, и никакой кнопки «обновить» вы не нажимаете.
Сцена работает ровно так же. Поменяйте цвет объекта — и отреагирует только то, что читало этот цвет. Не объект, а именно цвет.
изменили material.color
│
├─ рендерер перекрасил эту поверхность
├─ поле инспектора, показывающее его, обновилось
├─ изменение поставлено в очередь для ваших соавторов
└─ …и больше вообще ничего не выполнилось
Что это вам даёт
Ничто не опрашивается и ничто не перерисовывается на всякий случай. Нет покадрового прохода, сравнивающего мир с его же копией, чтобы понять, что сдвинулось. Кадр делает ту работу, которой требуют изменения, а неподвижная сцена стоит почти ничего.
Большие сцены остаются отзывчивыми, пока вы их правите. Таймлайн с двадцатью тысячами кейфреймов не перерисовывается из-за того, что один из них сдвинулся: панель может зависеть от «кейфреймы изменились», не подписываясь на каждый кейфрейм списка. Именно это различие и держит большие проекты пригодными к работе.
Загрузке не нужен код загрузки. Система просит ресурс и не получает ничего, если он ещё не приехал; когда он приезжает, система просто выполняется снова. Никто не пишет колбэков, не опрашивает флаг готовности и не обрабатывает случай «ещё не загружено» дважды.
Один поток изменений кормит всё. Рендерер, инспектор, отмена действий, ваши соавторы, песочница скриптов и симуляция физики читают одни и те же обновления — поэтому они не могут разойтись или разойтись во мнениях о том, что в сцене. Отмена — не особый путь, это ещё одно изменение, идущее по той же трубе.
Забыть сказать движку невозможно. Нет «пометить грязным», нет «обновить», нет ручной инвалидации, о которой надо помнить. Запись значения и есть уведомление.
Обновление значения сливается с тем, что уже есть, а не заменяет его, поэтому всё, что наблюдало, остаётся прикреплённым к тому же самому, за чем наблюдало. Звучит как деталь реализации; на деле это разница между плавной анимацией и частотой кадров, проседающей по мере роста сцены.
Это не фича фреймворка — это основание
Реактивность — отдельный пакет, и тот же самый доступен вам. Панель плагина, расширение-компонент и собственный инспектор редактора написаны на нём, так что написанное вами расширение обновляется ровно по той же причине, что и встроенные панели.
import { signal, computed, effect, batch } from '@was/signals';
const score = signal(0);
const label = computed(() => `Score: ${score.value}`);
effect(() => render(label.value)); // перезапускается только при изменении счёта
batch(() => { score.value += 1; score.value += 1; }); // одно обновление, а не два
Три свойства стоит назвать, потому что у большинства реактивных систем есть одно или два из них:
| Тонкая гранулярность | зависимости отслеживаются на каждое прочитанное значение, а не на компонент. Ничто не перерисовывается «на всякий случай». |
| Глубина | реактивным становится целое дерево объекта, так что следить за position.y — не то же самое, что следить за объектом |
| Дешёвая зависимость | можно зависеть от того, что что-то изменилось, не читая это, — именно это не даёт огромному списку перерисовываться, когда сдвинулся один элемент |
Последнее звучит туманно и является причиной того, что таймлайн на двадцать тысяч кейфреймов остаётся редактируемым.
Валидация, которая не стоит вам кадра
Каждый компонент описан схемой, а схемы обычно означают налог: что-то во время работы обходит описание, поле за полем, каждый раз, когда приходят данные.
Здесь схема компилируется один раз в код ровно под эту форму. Обхода нет. Разница не академическая — измерено на собственном бенчмарке проекта:
| Разбор | Добавляется в бандл | |
|---|---|---|
| Обычная библиотека (zod) | 94,7 нс на операцию | +267 КБ минифицированного, +61 КБ в gzip |
| Эта (svdt) | 5,0 нс на операцию | ничего — она уже там |
Примерно в девятнадцать раз быстрее, и вашим посетителям не нужно ничего дополнительно скачивать.
Это важно, потому что валидация здесь не редкое событие: она выполняется на каждом изменении, пересекающем границу между редактором, песочницей скриптов, симуляцией физики и вашими соавторами. Стоимость на поле, кажущаяся ничтожной, съедает бюджет анимации, когда происходит тысячи раз в секунду.
Это тот же пакет, который ваш плагин может импортировать для собственных данных. → Общие пакеты
Тяжёлая работа уходит с кадра
Две вещи, которые обычно тормозят страницу в браузере, вынесены с неё:
- Ваши скрипты выполняются изолированно от страницы, поэтому медленный цикл не заморозит отрисовку.
- Физика считается рядом с кадром, а не внутри него.
Сцена остаётся отзывчивой, пока она занята, вместо того чтобы терять кадры, когда ваша логика становится интересной.
Рантайм отказывается от работы, пока она не нужна
Это та часть, которая для ваших посетителей выглядит как время загрузки.
Физика не запускается и не скачивается, пока она сцене реально не нужна. Декорации без движущихся частей никогда не платят за симулятор — а это мегабайты, которых ваши посетители не ждут. Проверка живая: мяч, созданный посреди сессии, поднимает физику для себя сам.
Десять тысяч копий стоят примерно как одна. Трава, толпы, обломки и частицы рисуются одним вызовом. Их расстановка генерируется из сида, а не хранится, так что она ничего не стоит в файле, ничего в сети и выглядит одинаково у каждого участника.
Ничего на уровне отдельной частицы никогда не синхронизируется. Одни и те же настройки дают один и тот же эффект везде, поэтому сложный эффект в мультиплеере бесплатен.
Один словарь для поведения
События, графы патчей и скрипты слышат одни и те же триггеры — on-click означает одно и то
же во всех трёх. Вы учите один набор имён, а потом выбираете, сколько кода хотите написать.
Это же означает, что взаимодействие можно начать как событие, повысить до патча, когда понадобится условие, и уйти в скрипт, когда понадобится настоящая логика, — ничего не переучивая.
Сделано не для одного человека
Совместная работа — не прикрученная сверху фича: space — это один живой мир, общий для всех, кто в нём, так что правки появляются по мере их внесения, а отмена действий работает у каждого своя.
Та же машинерия синхронизирует редактор, песочницу скриптов и симуляцию физики — поэтому они не могут разойтись во мнениях о том, что содержит сцена.
Он работает там, где ваша аудитория уже есть
Опубликованный проект открывается по ссылке, в браузере, без установки чего-либо. В этом и весь смысл WebAR: никакого магазина приложений, никакой загрузки, никакого «сначала установите наше приложение» между посетителем и тем, что вы сделали.
А дальше тот же проект достаёт дальше, и пересобирать его не надо:
| Где | Как он туда попадает |
|---|---|
| Любой браузер телефона | опубликованная ссылка или QR-код — устанавливать нечего |
| iOS App Clip | открывается по ссылке, QR-коду или NFC, без установки из App Store |
| Нативное приложение | нативный рендерер — см. ниже |
| Браузеры шлемов | та же опубликованная ссылка — рантайм говорит на WebXR |
| AR-очки и шлемы | нативный XR-слой для XREAL One, Quest 3 и PICO 4 — см. ниже |
| Шлемы через Unity | Unity SDK — XREAL, PICO, Apple Vision Pro, HoloLens 2, Magic Leap 2, Rokid |
Человек наводит камеру на код, и ваш проект открывается: ни магазина, ни аккаунта, ни ожидания. Apple ограничивает размер клипа, так что держите первую сцену лёгкой и подгружайте остальное, когда она уже работает.
Телефоны работают из коробки. Пакеты под конкретные устройства — конвейеры рендеринга, профили контроллеров, шаблоны деплоя — выдаются на аккаунт. → Поддержка платформ
На телефоне — нативная скорость рендеринга
В браузере вы получаете WebGL: он быстр и у него есть потолок. Нативное приложение вообще не рендерит через браузер: оно использует нативный рендерер и на iOS, и на Android, разговаривая с Metal и Vulkan напрямую.
Так что тот же самый проект получает графический бюджет нативного приложения — тени в реальном времени, более тяжёлые материалы, больше объектов на экране — а вам не приходится поддерживать вторую его версию. Ваши материалы компилируются и под этот рендерер, и под веб, — именно это делает «тот же самый проект» правдой, а не пожеланием.
Веб-сборку используйте ради охвата, приложение — ради проектов, которым нужен бюджет кадра.
Очки, нативно
Нативный XR-слой ставит сам рантайм AR Clip на очки: тот же проект, та же сцена, отрисованная на устройстве, а не в браузере.
Он построен как один интерфейс со сменными вендорскими бэкендами, так что устройство — это бэкенд, а не форк рантайма, и через Unity это не идёт:
| Устройство | Через | Что там работает |
|---|---|---|
| XREAL One | вендорский SDK | вывод, трекинг головы, камера, запись сессии |
| Meta Quest 3 | OpenXR | вывод, трекинг, контроллеры, трекинг рук, запись сессии |
| PICO 4 | OpenXR | то же, но с выключенной камерой |
Бэкенд объявляет то, что реально поддерживает, а не притворяется. У PICO здесь нет сквозного видео с камеры, так что сцене, которой нужен реальный мир позади, место на XREAL или Quest; а сцене, которой не нужен, — на всех трёх.
Через что бы посетитель ни пришёл, это одна и та же сцена с теми же компонентами и тем же поведением. Вы не поддерживаете веб-версию и версию для шлема.
И он расширяем там, где это важно
Собственные шаги и триггеры можно регистрировать из приложения. Редактор принимает плагины, которые добавляют панели, генерируют сцены или привносят совершенно новые виды компонентов. У каталога нод есть аварийный люк для того единственного случая, который он не покрывает.
Каждая таблица компонентов, триггеров, шагов и нод на этом сайте генерируется из самого движка. Появляется у движка нода — в справочнике появляется строка, и помнить об обновлении никому не нужно.
Дальше: Объекты и сцены — модель целиком.