JavaScript în Engine
API-ul de script al Engine-ului: ciclul de viață, capcanele de coordonate, cele trei moduri de a crea un obiect, și cum să muți unul fără să treci prin pereți.
Într-o propoziție#
Fiecare entitate poate purta un script JavaScript. Rulează în browser, în fila ta, împotriva scenei vii, și este API-ul canonic de scriptare: dialectul BASIC, și vederile Python, C# și Rust din editor, toate se compilează spre aceeași suprafață.
Lista exhaustivă este referința de API: 538 de membri în 50 de spații de nume, câte o pagină pentru fiecare, cu o căutare. Această pagină este ce trebuie să știi înainte de a o citi.
Ciclul de viață#
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 sunt globale; getComponent(type)
citește o componentă de pe entitatea care deține scriptul.
Coliziunile și trigger-ele se declanșează atât în 2D cât și în 3D, iar
fiecare parte primește propria normală de contact: other.contact.normal
indică de la entitatea cealaltă către aceasta. Asta îi permite unui script
să deosebească „am aterizat pe el" de „a aterizat el pe mine", o strivire de
platformer citește other.contact.normal.y > 0.5.
Cele două capcane de coordonate#
Amândouă costă timp real de depanare, și niciuna nu eșuează zgomotos.
Mouse: pagină versus canvas#
input.mousePosition este în coordonate de pagină. scene.pick() vrea
coordonate de canvas. Trecerea uneia în locul celeilalte nu ratează, ci
lovește un punct decalat cu poziția viewport-ului în fereastră, așa că un
click-to-move al eroului merge undeva plauzibil și greșit.
Apelează scene.pick() fără niciun argument (țintește cursorul), sau
folosește input.mouseViewport când ai nevoie de numere.
Controalele virtuale sunt în pixeli de canvas#
input.addVirtualJoystick(id, x, y, size) și input.addVirtualButton(...)
își plasează centrele în același spațiu ca mouseViewport. Un joystick
întoarce un vector X/Y normalizat după zona lui moartă radială; un buton
expune held, pressed-this-frame și released-this-frame separat.
Pointer-ul lor este capturat și consumat, așa că o atingere pe control nu
este raportată și ca o acțiune brută de canvas.
Joystick-ul implicit este unificat.
input.getJoystickX/Y()alege un singur vector complet din stick-ul de pe ecran, un gamepad fizic, sau tastatura (WASD/săgeți), oricare dintre surse este mai puternică. Un gamepad conectat, dar inactiv, nu dezactivează deci mișcarea de la tastatură. Axa lui Y urmează spațiul de ecran 2D: sus este-1.
Trei moduri de a crea un obiect, și nu sunt interschimbabile#
| Ce creează | Trăiește pentru | |
|---|---|---|
scene.createEntity(def) | o entitate autorizată: apare în Scene Graph și în Inspector | proiect, este salvată |
game.spawn(type, pos) | o primitivă goală: fără viață, fără AI, fără comportament | sesiunea de joc |
game.spawnFrom(template, pos, opts) | o copie a unei entități întregi: componente, corp fizic, tag-uri, layer, scală și stiva de comportament compilată | sesiunea de joc |
game.spawn este potrivit pentru moloz și prototipuri și inutil pentru
inamici: un spawner alimentat cu el produce bile inerte. Șablonul pe care
spawnFrom îl copiază este o entitate obișnuită pe care o compui în editor
și o oprești cu ochiul din ierarhie: fără un format separat, fără un al
doilea editor, așa că ce vezi este ce apare.
// 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 acceptă un id, un nume sau un tag. Copiile făcute în timpul
jocului sunt limitate la sesiunea de joc, așa că stop() le elimină, iar
scena de editare nu este niciodată poluată.
Dezactivarea unei entități înseamnă ascunsă ȘI tăcută: nod dezactivat, script oprit, corp fizic care nu mai răspunde. Doar ascunderea ar lăsa un inamic invizibil care continuă să te lovească.
game.setEnabled(id, on)o pilotează dintr-un script, ochiul din ierarhie o pilotează de acolo.
Mutarea a ceva: împinge un corp, nu teleporta#
Scrierea lui transform.position în fiecare cadru trece prin pereți, prin
alți monștri și prin podea. Pilotează viteza în schimb, dar numai când
există ceva de pilotat:
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 fără un corp umple un câmp pe care nimeni nu îl
integrează, iar entitatea se oprește pe loc. De aceea există
physics.hasBody, și de aceea fiecare comportament de mișcare din catalog
pune întrebarea, în loc să presupună un răspuns. Oprirea înseamnă
anularea vitezei: a nu face nimic lasă corpul să alunece mai departe.
Scrierea unei poziții chiar mișcă acum un corp fizic. Havok pilotează transformarea, niciodată invers, așa că atribuirea lui
transform.positionpe un corp dinamic era suprascrisă la pasul următor: fiecare teleportare eșua tăcut pe o entitate fizică. Proxy-ul de poziție resincronizează acum corpul, așa cum a făcut-o mereu proxy-ul de rotație.
Pathfinding-ul este un instantaneu#
pathfinding.createGrid() face raycast pe o grilă o singură dată,
centrată acolo unde a fost apelată. Ieșirea din acea cutie întoarce null,
iar majoritatea apelanților revin apoi la o linie dreaptă, prin pereți, fără
niciun cuvânt.
Grila se recentrează la fiecare traseu cerut, implicit.
setAutoRecenter(false) renunță la asta pe o lume fixă, unde reconstrucția
costă o trecere de raycast degeaba.
console.log scrie în jurnal. Print desenează.#
Consola este console.log / warn / error, ca oriunde altundeva. Print
din BASIC desenează pe ecran și este apelat în fiecare cadru, cele două
sunt comenzi separate în mod deliberat, iar
confundarea lor este ceea ce umple o consolă cu o buclă de cadru.
Unde rulează un script#
În editor și într-un build web, scripturile rulează în browser. Într-un build nativ, desktop sau mobil, rulează neschimbate pe un runtime QuickJS integrat, aceeași sursă, nu un port.
Scripturile sunt stocate în propria bază de date a browserului cât timp editezi, așa că un buffer nesalvat supraviețuiește unei reîncărcări; sunt salvate în proiect odată cu tot restul.
Limite și capcane frecvente#
scene.pick()cu un argument vrea coordonate de canvas. Vezi mai sus; este cel mai frecvent bug de loc greșit.game.spawnface o primitivă, nu un personaj. FoloseștespawnFrom.- Un corp este creat pentru entitățile născute în timpul jocului. Fizica se construia înainte doar la Play, așa că orice apărut ulterior cădea tăcut prin lume, în timp ce vecinii lui plasați manual se comportau normal.
- Opt pași este plafonul coechipierului AI, nu al tău. O buclă de script
nu are o astfel de limită; un
whilescăpat de sub control va bloca cadrul ca oriunde altundeva. - Celelalte patru vederi de limbaj sunt transpilate din aceasta. Editarea unei vederi Python sau Rust și revenirea trece prin JavaScript-ul stocat, vezi pagina BASIC pentru motiv.