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

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

Напишите небольшой скрипт, который вращает объект, когда на него нажимают. Потом добавьте настройки в инспектор и посмотрите, чем может пользоваться скрипт.

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

Вот готовый скрипт. Нажмите на объект, и он начнёт вращаться:

Scripts/spinner.ts
import { TransformComponent } from '@was/engine';

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 доступны всегда. Импортировать их не нужно. Классы компонентов, например TransformComponent, берутся из @was/engine.

Как читать и менять объекты​

const transform = ctx.entity.getComponent(TransformComponent);

transform.position; // прочитать одно значение
transform.$data; // прочитать всё целиком как обычный объект
transform.update({ … }); // записать

На двух ошибках спотыкаются почти все.

Если поменять одно вложенное значение, остальные обнулятся

update сохраняет поля, которые вы не передали, например rotation. Но переданное значение целиком заменяет старое. Поэтому update({ position: { y: 2 } }) заодно вернёт x и z к значению по умолчанию — нулю.

Скопируйте значения, которые нужно сохранить, через ...:

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';

Можно импортировать классы компонентов движка. А ещё можно импортировать другие ресурсы по их пути:

  • другой скрипт отдаёт то, что он экспортирует;
  • патч отдаёт свой код — тот же, что показывает панель Код;
  • всё остальное отдаёт ссылку { id, type }. Передайте её в ctx.spawn или передайте её id в ctx.audio.play.

Если вы переименуете или переместите ресурс, эти импорты обновятся сами.

Что скрипту недоступно​

Тяжёлый скрипт не замедлит картинку. Вот что скриптам недоступно:

  • window, document, DOM;
  • fetch, localStorage и любой доступ к сети;
  • библиотеки рендеринга — сцена меняется через компоненты, а не рисованием;
  • таймеры браузера — используйте вместо них ctx.tick.

Вместо этого используйте:

  • для интерфейса — карточки интерфейса;
  • для звука — ctx.audio;
  • чтобы хранить значения — ctx.store и глобальные переменные.

Когда скрипт запускается и останавливается​

Скрипт запускается, когда его объект действительно на экране: ничего выше него не выключено, а его сцена активна. Когда это перестаёт быть так, скрипт останавливается.

on-launch срабатывает сразу, как только скрипт запустился, так что вы его не пропустите.

Функция, которую вы возвращаете из init, выполняется, когда скрипт останавливается. Используйте её, чтобы навести порядок: отписаться, остановить звуки, очистить состояние.

В редакторе скрипты не работают

Редактор показывает сцену, но не выполняет никакой логики. Поправьте скрипт, а потом откройте Предпросмотр. Горячей перезагрузки нет: запущенный проект подхватит изменения при следующем перезапуске.


Дальше: Куда прикрепить скрипт — какой уровень выбрать для скрипта.