JavaScript в Engine
API скриптів Engine: життєвий цикл, пастки координат, три способи створити об'єкт, і як пересунути його, не проходячи крізь стіни.
Одним рядком#
Кожна сутність може нести скрипт на JavaScript. Він виконується в браузері, у вашій вкладці, проти живої сцени, і саме це канонічний API скриптингу: діалект BASIC і перегляди Python, C# та Rust у редакторі, усі компілюються в ту саму поверхню.
Вичерпний список: довідник API: 538 елементів у 50 просторах імен, по одній сторінці на кожен, із пошуком. Ця сторінка це те, що варто знати перед тим, як його читати.
Життєвий цикл#
function start() {} // once, when the entity starts
function update(dt) {} // every frame, dt in seconds
function fixedUpdate(dt) {} // fixed timestep, for physics
function onCollisionEnter(other) {} // first frame of contact
function onCollisionStay(other) {} // every frame while touching
function onCollisionExit(other) {} // contact just ended
function onTriggerEnter(other) {} // entered a trigger volume
function onTriggerExit(other) {}
function onMessage(msg, data, senderId) {} // entity-to-entity messaging
function onAnimationEvent(name, data) {}
function onDestroy() {}
function main() {} // console-game entry, async allowed
deltaTime, time, entityId і entity: глобальні змінні;
getComponent(type) читає компонент із сутності, якій належить скрипт.
Зіткнення й тригери спрацьовують і в 2D, і в 3D, і кожна сторона отримує
власну нормаль контакту: other.contact.normal вказує від іншої сутності
до цієї. Саме це дозволяє скрипту відрізнити «я приземлився на нього» від
«він приземлився на мене»: тупання платформера читає
other.contact.normal.y > 0.5.
Дві пастки з координатами#
Обидві коштують реального часу на налагодження, і жодна не провалюється голосно.
Миша: сторінка проти полотна#
input.mousePosition подає координати сторінки. scene.pick() хоче
координати полотна. Передача одного замість іншого не промахується
повністю: вона влучає в точку, зміщену на позицію вікна перегляду у вікні
браузера, тож клік-і-йди герой іде кудись правдоподібне, але неправильне.
Викликайте scene.pick() без аргументу (вона націлюється на курсор), або
використовуйте input.mouseViewport, коли потрібні самі числа.
Віртуальні елементи керування в пікселях полотна#
input.addVirtualJoystick(id, x, y, size) і input.addVirtualButton(...)
розміщують свої центри в тому самому просторі, що й mouseViewport.
Джойстик повертає нормалізований X/Y після своєї радіальної мертвої зони;
кнопка виставляє held, pressed-this-frame і released-this-frame
окремо. Їхній вказівник захоплюється й споживається, тож дотик до елемента
керування не звітується ще й як сирова дія полотна.
Типовий джойстик уніфікований.
input.getJoystickX/Y()обирає один цілісний вектор із екранного стіка, фізичного геймпада або клавіатури (WASD/стрілки), залежно від того, яке джерело сильніше. Тому неактивний підключений геймпад не вимикає керування з клавіатури. Його вісь Y слідує за 2D-простором екрана: вгору це-1.
Три способи створити об'єкт, і вони не взаємозамінні#
| Що створює | Живе | |
|---|---|---|
scene.createEntity(def) | авторську сутність: вона з'являється в Scene Graph та в Inspector | у проєкті, зберігається |
game.spawn(type, pos) | голий примітив: без здоров'я, без AI, без поведінки | у сесії гри |
game.spawnFrom(template, pos, opts) | копію цілої сутності: компоненти, фізичне тіло, теги, шар, масштаб і скомпільований стек поведінки | у сесії гри |
game.spawn годиться для уламків і прототипів і марна для ворогів:
спавнер, наповнений нею, виробляє інертні кульки. Шаблон, який копіює
spawnFrom, це звичайна сутність, яку ви складаєте в редакторі й вимикаєте
оком в ієрархії: жодного окремого формату, жодного другого редактора, тож
що бачите, те й отримуєте.
// Once, at start: a missing template should say so, not fail every wave silently
if (!game.hasTemplate('Skeleton')) console.warn('no Skeleton template');
const mob = game.spawnFrom('Skeleton', { x, y, z }, { tag: 'Enemy' });
template приймає id, ім'я або тег. Копії, зроблені під час гри, живуть
лише в межах сесії, тож stop() їх прибирає, і сцена редагування ніколи не
забруднюється.
Вимкнення сутності означає ПРИХОВАНУ Й БЕЗМОВНУ: вузол вимкнений, скрипт зупинений, фізичне тіло більше не реагує. Саме лише приховування залишило б невидимого ворога, який досі вас б'є.
game.setEnabled(id, on)керує цим зі скрипту, оком в ієрархії, з інтерфейсу.
Пересування чогось: штовхайте тіло, не телепортуйте#
Запис у transform.position щокадру проходить крізь стіни, крізь інших
монстрів і крізь підлогу. Замість цього керуйте швидкістю, але лише
тоді, коли є чим керувати:
if (physics.hasBody) {
const v = physics.getVelocity();
physics.setVelocity({ x: vx, y: v.y, z: vz }); // pushes a real body
} else {
transform.position.x += vx * dt; // legitimate for a flyer
}
physics.setVelocity без тіла заповнює поле, яке ніхто не інтегрує, і
сутність зупиняється намертво. Саме тому існує physics.hasBody, і саме
тому кожна рухома поведінка в каталозі ставить це питання, а не припускає
відповідь. Зупинка означає скасування швидкості: бездіяльність залишає
тіло котитися далі.
Запис позиції тепер справді рухає фізичне тіло. Havok керує трансформацією, а не навпаки, тож присвоєння
transform.positionдинамічному тілу раніше перезаписувалося на наступному кроці: кожен телепорт мовчки провалювався на фізичній сутності. Проксі позиції тепер синхронізує тіло заново, так само, як завжди робив проксі обертання.
Пошук шляху як знімок#
pathfinding.createGrid() виконує рейкаст сітки один раз, з центром там,
де його викликали. Вихід за межі цієї коробки повертає null, і більшість
викликачів тоді переходять на пряму лінію, крізь стіни, без жодного слова.
Сітка за замовчуванням перецентровується на кожен запитаний шлях.
setAutoRecenter(false) відмовляється від цього на нерухомому світі, де
перебудова коштує проходу рейкастів даремно.
console.log пише в консоль. Print малює.#
Консоль це console.log / warn / error, як і будь-де. BASIC-команда
Print малює на екрані і викликається щокадру, ці дві
навмисно окремі команди, і сплутати їх це те, що
заповнює консоль кадровим циклом.
Де виконується скрипт#
У редакторі й у веб-збірці скрипти виконуються в браузері. У нативній збірці для десктопу чи мобільних вони виконуються без змін на вбудованому рантаймі QuickJS, той самий код, а не порт.
Скрипти зберігаються у власній базі даних браузера, поки ви редагуєте, тож незбережений буфер переживає перезавантаження; вони зберігаються в проєкт разом із усім іншим.
Обмеження та типові пастки#
scene.pick()з аргументом хоче координати полотна. Див. вище, це найпоширеніша помилка не тим місцем.game.spawnстворює примітив, а не персонажа. ВикористовуйтеspawnFrom.- Тіло створюється для сутностей, народжених посеред гри. Раніше фізика будувалася лише при Play, тож усе, породжене пізніше, мовчки провалювалося крізь світ, поки його заздалегідь розміщені сусіди поводилися нормально.
- Вісім кроків це стеля AI-напарника, не ваша. У циклу скрипту такого
обмеження немає; неконтрольований
whileпідвісить кадр так само, як будь-де. - Чотири інші мовні перегляди транспільовані з цього. Редагування перегляду Python чи Rust і повернення назад проходить крізь збережений JavaScript, див. сторінку BASIC, чому саме так.