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

Первый скрипт

Скрипт — это файл на TypeScript, который вы прикрепляете к объекту. Он выполняется в плеере, по одному экземпляру на каждый объект, который его использует.

Вот полноценный пример: нажимаете на объект — и он начинает крутиться.

Scripts/spinner.ts
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. Работающий проект подхватит изменённый скрипт при следующем перезапуске.


Дальше: Где живёт скрипт