Первый скрипт
Скрипт — это файл на TypeScript, который вы прикрепляете к объекту. Он выполняется в плеере, по одному экземпляру на каждый объект, который его использует.
Вот полноценный пример: нажимаете на объект — и он начинает крутиться.
init((ctx) => {
const props = defineProps({
speed: { type: 'number', default: 1, min: 0, label: 'Speed' },
});
let spinning = false;
ctx.on('on-click', () => {
spinning = !spinning;
});
ctx.tick((dt) => {
if (!spinning) return;
const t = ctx.entity.getComponent(TransformComponent);
t?.update({
rotation: { ...t.$data.rotation, y: t.$data.rotation.y + props.speed * dt },
});
});
return () => {
// по желанию: прибраться, когда этот объект исчезает
};
});
Здесь происходят четыре вещи:
init | получает ctx — вашу ручку к работающей сцене. Всё начинается отсюда |
defineProps | объявляет настройки, которые редактор показывает в инспекторе, заполняемые для каждого объекта |
ctx.on | подписывается на триггер — те же триггеры, что у событий и патчей |
ctx.tick | выполняется каждый кадр, dt в секундах |
init и defineProps доступны всегда — импортировать их не нужно.
Чтение и запись объектов
const transform = ctx.entity.getComponent(TransformComponent);
transform.position; // прочитать одно значение
transform.$data; // прочитать всё целиком как обычный объект
transform.update({ … }); // записать
Две ловушки, и они ловят всех:
update({ position: { y: 2 } }) заодно обнулит x и z — вы передали целую новую позицию, в
которой заполнен только y.
Разворачивайте то, что хотите сохранить:
transform.update({ position: { ...transform.$data.position, y: 2 } });
material.update({ color }) не делает вообще ничего, потому что color живёт внутри слота, а не
наверху. Пишите слот:
material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
И никогда не присваивайте в $data напрямую. Выглядит так, будто сработало, а изменение
отбрасывается.
Настройки в инспекторе
defineProps — это то, что делает скрипт достойным переиспользования: один и тот же скрипт на
десяти объектах, настроенный на каждом по-своему, без копипасты.
const props = defineProps({
speed: { type: 'number', default: 1, min: 0, softMax: 10, suffix: 'm/s' },
target: { type: 'entity', label: 'Look at' },
sound: { type: 'resource', resource: 'audio' },
mode: { type: 'select', options: ['chase', 'patrol'], default: 'patrol' },
});
| Тип | Редактор показывает | Скрипт получает |
|---|---|---|
number | поле или слайдер | число |
string | текстовое поле | строку |
boolean | переключатель | логическое значение |
color | выбор цвета | строку с цветом |
select | выпадающий список | один из ваших вариантов |
entity | выбор объекта | сам объект, готовый к использованию |
scene | выбор сцены | объект сцены |
resource | выбор ресурса | ссылку, которую можно передать дальше |
array | список | массив |
group | блок с заголовком | вложенный объект |
Стоит знать: label и help для панели, min и max для настоящих ограничений, softMax —
где заканчивается слайдер, не запрещая ввести больше вручную, showWhen — показать поле, только
когда у другого определённое значение, и group — чтобы длинный список оставался опрятным.
defineProps надо писать буквальноРедактор читает ваши настройки, не выполняя скрипт, поэтому ему нужно видеть их напрямую, а не собранными из переменных или возвращёнными функцией.
Значения читаются заново каждый раз, так что props.speed всегда отражает то, что сейчас стоит в
инспекторе.
Импорты
import { TransformComponent } from '@was/engine';
import helpers from 'Scripts/helpers';
Можно импортировать классы компонентов движка и другие ресурсы по пути: другой скрипт отдаст
свои экспорты, патч — скомпилированный модуль, а всё остальное — ссылку, которую можно передать в
ctx.spawn или ctx.audio.play.
Переименуйте или переместите ресурс — эти импорты обновятся сами.
Что даёт песочница, а чего нет
Скрипты выполняются изолированно от страницы, что не даёт тяжёлому скрипту застопорить рендеринг. Поэтому недоступны:
window,document, DOM;fetch,localStorage, доступ к сети в любом виде;- любые библиотеки рендеринга — сцена меняется через компоненты, а не рисованием;
- таймеры браузера — вместо них
ctx.tick.
Чем пользоваться вместо этого: карточками интерфейса для
интерфейса, ctx.audio для звука, ctx.store и глобальными значениями, чтобы что-то хранить.
Когда скрипт запускается и останавливается
Экземпляр создаётся, когда его объект жив и на экране — ничто выше не выключено, сцена активна, — и уничтожается, когда это перестаёт быть правдой.
on-launch срабатывает сразу после создания, так что вы его никогда не пропустите. Функция,
которую вы возвращаете из init, — это ваша уборка: отписаться, остановить звуки, сбросить
состояние.
Редактор рисует сцену, но не исполняет логику. Отредактировали — открывайте Preview. Работающий проект подхватит изменённый скрипт при следующем перезапуске.
Дальше: Где живёт скрипт