JavaScript w Engine
API skryptowe Engine, cykl życia, pułapki związane ze współrzędnymi, trzy sposoby tworzenia obiektu i jak poruszać nim, nie przechodząc przez ściany.
W jednym zdaniu#
Każda encja może nosić skrypt JavaScript. Działa w przeglądarce, w twojej karcie, na żywej scenie, i jest kanonicznym API skryptowym: dialekt BASIC oraz widoki Python, C# i Rust w edytorze wszystkie kompilują się do tej samej powierzchni.
Wyczerpująca lista to dokumentacja API: 538 elementów w 50 przestrzeniach nazw, po jednej stronie na każdą, z wyszukiwarką. Ta strona to to, co warto wiedzieć zanim się do niej zajrzy.
Cykl życia#
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 i entity to zmienne globalne;
getComponent(type) odczytuje komponent z encji, do której należy skrypt.
Kolizje i triggery odpalają się zarówno w 2D, jak i w 3D, a każda strona
dostaje własną normalną kontaktu: other.contact.normal wskazuje od tamtej
encji ku tej. To właśnie pozwala skryptowi odróżnić "wylądowałem na nim" od
"on wylądował na mnie": tupnięcie w platformówce odczytuje
other.contact.normal.y > 0.5.
Dwie pułapki związane ze współrzędnymi#
Obie kosztują realny czas debugowania, i żadna nie zawodzi głośno.
Mysz: page kontra canvas#
input.mousePosition jest we współrzędnych page. scene.pick() chce
współrzędnych canvas. Podanie jednych zamiast drugich nie chybia całkiem,
trafia w punkt przesunięty o pozycję viewportu w oknie, więc bohater
poruszany kliknięciem idzie w miejsce, które wygląda wiarygodnie, ale jest
złe.
Wywołaj scene.pick() bez argumentu (celuje wtedy w kursor), albo użyj
input.mouseViewport, gdy potrzebujesz samych liczb.
Wirtualne sterowanie jest w pikselach canvas#
input.addVirtualJoystick(id, x, y, size) i input.addVirtualButton(...)
umieszczają swoje środki w tej samej przestrzeni co mouseViewport. Joystick
zwraca znormalizowane X/Y po uwzględnieniu swojej promienistej martwej
strefy; przycisk osobno udostępnia held, pressed-this-frame i
released-this-frame. Ich wskaźnik jest przechwytywany i zużywany, więc
dotknięcie kontrolki nie jest jednocześnie zgłaszane jako surowa akcja canvas.
Domyślny joystick jest ujednolicony.
input.getJoystickX/Y()wybiera jeden kompletny wektor z gałki na ekranie, fizycznego gamepada albo klawiatury (WASD/strzałki), zależnie od tego, które źródło jest najsilniejsze. Bezczynny, podłączony pad nie wyłącza więc ruchu z klawiatury. Jego oś Y podąża za przestrzenią ekranu 2D: góra to-1.
Trzy sposoby na stworzenie obiektu, i nie są wymienne#
| Co tworzy | Jak długo żyje | |
|---|---|---|
scene.createEntity(def) | encję autorską: pojawia się w Scene Graph i w Inspector | projekt, jest zapisywana |
game.spawn(type, pos) | goły prymityw: bez zdrowia, bez AI, bez zachowania | sesję gry |
game.spawnFrom(template, pos, opts) | kopię całej encji: komponenty, ciało fizyczne, tagi, warstwę, skalę i skompilowany stos zachowań | sesję gry |
game.spawn sprawdza się do gruzu i prototypów, i jest bezużyteczny dla
przeciwników: spawner nim zasilany produkuje bezwładne kulki. Szablon, który
kopiuje spawnFrom, to zwykła encja, którą komponujesz w edytorze i wyłączasz
okiem w hierarchii: bez osobnego formatu, bez drugiego edytora, więc to, co
widzisz, jest tym, co się pojawia.
// 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 przyjmuje id, nazwę albo tag. Kopie zrobione podczas gry są
ograniczone do sesji gry, więc stop() je usuwa, a scena edycji nigdy nie
zostaje zaśmiecona.
Wyłączenie encji oznacza ukrytą I wyciszoną: węzeł wyłączony, skrypt zatrzymany, ciało fizyczne przestaje reagować. Samo ukrycie zostawiłoby niewidzialnego przeciwnika, który wciąż by cię trafiał.
game.setEnabled(id, on)steruje tym ze skryptu, ikona oka steruje tym z hierarchii.
Poruszanie czymś: pchnij ciało, nie teleportuj#
Zapisywanie transform.position co klatkę przechodzi przez ściany, przez
inne potwory i przez podłogę. Zamiast tego steruj prędkością, ale tylko
wtedy, gdy jest czym sterować:
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 bez ciała wypełnia pole, którego nikt nie całkuje, i
encja zatrzymuje się na amen. Dlatego istnieje physics.hasBody, i dlatego
każde zachowanie ruchu w katalogu zadaje to pytanie, zamiast zakładać
odpowiedź. Zatrzymanie oznacza wyzerowanie prędkości: nicnierobienie
zostawia ciało w ruchu bezwładnym.
Zapisanie pozycji teraz naprawdę porusza ciałem fizycznym. Havok steruje transformacją, nigdy odwrotnie, więc przypisanie
transform.positionna dynamicznym ciele było kiedyś nadpisywane w kolejnym kroku: każdy teleport na fizycznej encji zawodził po cichu. Proxy pozycji resynchronizuje teraz ciało, tak jak zawsze robiło to proxy rotacji.
Pathfinding to migawka#
pathfinding.createGrid() wykonuje raycast siatki raz, wyśrodkowanej
tam, gdzie została wywołana. Opuszczenie tego pudełka zwraca null, a
większość wywołujących wtedy po cichu przechodzi na linię prostą, przez
ściany, bez słowa.
Siatka domyślnie centruje się ponownie przy każdej żądanej podróży.
setAutoRecenter(false) pozwala z tego zrezygnować w świecie o stałym
układzie, gdzie przebudowa kosztuje przebieg raycastów na darmo.
console.log loguje. Print rysuje.#
Konsola to console.log / warn / error, tak jak wszędzie indziej.
BASIC-owy Print rysuje na ekranie i jest wywoływany co klatkę; te dwa są
celowo osobnymi poleceniami, a pomylenie ich to
sposób na zapchanie konsoli pętlą klatek.
Gdzie działa skrypt#
W edytorze i w buildzie webowym skrypty działają w przeglądarce. W buildzie natywnym, desktopowym lub mobilnym, działają bez zmian na wbudowanym runtime QuickJS, to to samo źródło, nie port.
Skrypty są przechowywane we własnej bazie danych przeglądarki w trakcie edycji, więc niezapisany bufor przetrwa przeładowanie; zapisują się w projekcie razem z resztą.
Ograniczenia i częste problemy#
scene.pick()z argumentem chce współrzędnych canvas. Patrz wyżej; to najczęstszy błąd złego miejsca.game.spawntworzy prymityw, nie postać. UżyjspawnFrom.- Ciało jest teraz tworzone dla encji narodzonych w trakcie gry. Fizyka kiedyś była budowana tylko przy Play, więc wszystko, co pojawiło się później, po cichu przepadało przez świat, podczas gdy jego ręcznie rozmieszczeni sąsiedzi zachowywali się normalnie.
- Osiem kroków to sufit współpracownika AI, nie twój. Pętla skryptu nie
ma takiego limitu; rozpędzone
whilezawiesi klatkę tak samo jak wszędzie indziej. - Pozostałe cztery widoki językowe są transpilowane z tego jednego. Edycja widoku Python albo Rust i powrót przechodzi przez zapisany JavaScript, zobacz stronę o BASIC, dlaczego.