JavaScript i Engine

Engines script-API: livscyklussen, koordinatfælderne, de tre måder at lave et objekt på, og hvordan man flytter ét uden at gå gennem vægge.

I én sætning#

Enhver entitet kan bære et JavaScript-script. Det kører i browseren, i din fane, mod den levende scene, og det er den kanoniske scripting-API'en: BASIC-dialekten, og Python-, C#- og Rust-visningerne i editoren, kompilerer alle ned til den samme overflade.

Den udtømmende liste er API-referencen: 538 medlemmer i 50 namespaces, én side hver, med en søgning. Denne side er det, du skal vide, før du læser den.

Livscyklussen#

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 og entity er globale; getComponent(type) læser en komponent fra entiteten, der ejer scriptet.

Kollisioner og triggere fyrer i både 2D og 3D, og hver side modtager sin egen kontaktnormal: other.contact.normal peger fra den anden entitet mod denne. Det er det, der lader et script skelne "jeg landede på den" fra "den landede på mig", et platformer-stomp læser other.contact.normal.y > 0.5.

De to koordinatfælder#

Begge koster reel debugging-tid, og ingen af dem fejler højlydt.

Musen: side versus canvas#

input.mousePosition er i side-koordinater. scene.pick() vil have canvas-koordinater. At give den ene i stedet for den anden er ikke et komplet forbier, det rammer et punkt forskudt af viewportens position i vinduet, så en klik-for-at-flytte-helt går et sted, der er plausibelt og forkert.

Kald scene.pick() uden noget argument (den sigter mod markøren), eller brug input.mouseViewport, når du har brug for tallene.

Virtuelle kontroller er i canvas-pixels#

input.addVirtualJoystick(id, x, y, size) og input.addVirtualButton(...) placerer deres centre i samme rum som mouseViewport. Et joystick returnerer en normaliseret X/Y efter sin radiale dødzone; en knap eksponerer held, pressed-this-frame og released-this-frame hver for sig. Deres pointer opfanges og forbruges, så et tryk på kontrollen ikke også rapporteres som en rå canvas-handling.

Standard-joysticket er samlet. input.getJoystickX/Y() vælger én komplet vektor fra skærmens stick, en fysisk gamepad, eller tastaturet (WASD/piletaster), hvad end der er stærkest. En inaktiv, tilsluttet pad deaktiverer derfor ikke tastaturbevægelse. Dens Y-akse følger 2D-skærmrum: op er -1.

Tre måder at lave et objekt på, og de kan ikke bruges om hinanden#

Hvad den laverLever i
scene.createEntity(def)en forfattet entitet: den optræder i Scene Graph og i Inspectorprojektet, den gemmes
game.spawn(type, pos)en bar primitiv: ingen health, ingen AI, ingen adfærdspilsessionen
game.spawnFrom(template, pos, opts)en kopi af en hel entitet: komponenter, physics body, tags, layer, skala og den kompilerede adfærdsstakspilsessionen

game.spawn er rigtig til debris og prototyper og ubrugelig til fjender: en spawner fodret af den producerer livløse kugler. Skabelonen, spawnFrom kopierer, er en almindelig entitet, du sammensætter i editoren og slår fra med hierarkiets øje: intet separat format, ingen anden editor, så det, du ser, er det, der optræder.

// 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 accepterer et id, et navn eller et tag. Kopier lavet under spillet er scoped til spillet, så stop() fjerner dem, og redigeringsscenen bliver aldrig forurenet.

At slå en entitet fra betyder skjult OG lydløs: node deaktiveret, script stoppet, physics body svarer ikke længere. Kun at skjule ville efterlade en usynlig fjende, der stadig rammer dig. game.setEnabled(id, on) styrer det fra et script, øjenikonet styrer det fra hierarkiet.

At flytte noget: skub et legeme, teleportér ikke#

At skrive transform.position hvert billede går gennem vægge, gennem andre monstre og gennem gulvet. Styr velocity i stedet, men kun når der er noget at styre:

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 uden et body udfylder et felt, ingen integrerer, og entiteten stopper dødt. Det er derfor, physics.hasBody findes, og hvorfor enhver bevægende adfærd i kataloget stiller spørgsmålet i stedet for at antage et svar. At stoppe betyder at annullere velocity: at gøre ingenting lader legemet fortsætte med at glide.

At skrive en position flytter nu rent faktisk et physics body. Havok styrer transformen, aldrig omvendt, så at tildele transform.position på et dynamisk body plejede at blive overskrevet i det næste step: enhver teleportering fejlede stiltiende på en fysisk entitet. Positions-proxyen synkroniserer nu legemet igen, ligesom rotations-proxyen altid har gjort.

Pathfinding er et snapshot#

pathfinding.createGrid() raycaster et gitter én gang, centreret hvor den blev kaldt. At forlade den boks returnerer null, og de fleste kaldere falder så tilbage på en lige linje, gennem vægge, uden et ord.

Gitteret rekentreres på den forespurgte tur som standard. setAutoRecenter(false) melder fra på en fast verden, hvor genopbygning koster en raycast-passage for ingenting.

console.log logger. Print tegner.#

Konsollen er console.log / warn / error, som alle andre steder. BASICs Print tegner på skærmen og kaldes hvert billede, de to er bevidst separate kommandoer, og at blande dem sammen er det, der fylder en konsol med et frame-loop.

Hvor et script kører#

I editoren og i en web-build kører scripts i browseren. I en native desktop- eller mobil-build kører de uændret på en indlejret QuickJS-runtime, den samme kildekode, ikke en portering.

Scripts gemmes i browserens egen database, mens du redigerer, så en ikke-gemt buffer overlever en genindlæsning; de gemmes ind i projektet med alt andet.

Grænser og almindelige faldgruber#

  • scene.pick() med et argument vil have canvas-koordinater. Se ovenfor; det er den enkeltmest almindelige forkert-sted-fejl.
  • game.spawn laver en primitiv, ikke en karakter. Brug spawnFrom.
  • Et body oprettes for entiteter født midt i spillet. Physics plejede kun at blive bygget ved Play, så alt, der blev spawnet bagefter, faldt stiltiende gennem verden, mens dets håndplacerede naboer opførte sig ordentligt.
  • Otte skridt er AI-holdkammeratens loft, ikke dit. Et script-loop har ingen sådan grænse; en løbsk while vil hænge billedet ligesom alle andre steder.
  • De fire andre sprogvisninger er transpileret fra denne. At redigere en Python- eller Rust-visning og skifte tilbage går gennem den gemte JavaScript, se BASIC-siden for hvorfor.