JavaScript i Engine

Engines skript-API: livscykeln, koordinatfällorna, de tre sätten att skapa ett objekt, och hur man flyttar ett utan att gå genom väggar.

I korthet#

Varje entitet kan bära ett JavaScript-skript. Det körs i webbläsaren, i din flik, mot den levande scenen, och det är det kanoniska skript-API:et: BASIC- dialekten, och vyerna Python, C# och Rust i editorn, kompileras alla ner till samma yta.

Den uttömmande listan är API-referensen: 538 medlemmar i 50 namnrymder, en sida var, med en sökning. Den här sidan är vad du behöver veta innan du läser den.

Livscykeln#

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 och entity är globaler; getComponent(type) läser en komponent från entiteten som äger skriptet.

Kollisioner och triggers utlöses i både 2D och 3D, och varje sida får sin egen kontaktnormal: other.contact.normal pekar från den andra entiteten mot den här. Det är vad som låter ett skript skilja "jag landade på den" från "den landade på mig", en plattformsstamp läser other.contact.normal.y > 0.5.

De två koordinatfällorna#

Båda kostar riktig felsökningstid, och ingen av dem misslyckas högljutt.

Musen: sida kontra canvas#

input.mousePosition är i sid-koordinater. scene.pick() vill ha canvas-koordinater. Att skicka den ena för den andra missar inte, den träffar en punkt förskjuten med vyfönstrets position i fönstret, så att en klicka-för-att-gå-hjälte går någonstans som ser rimligt men fel ut.

Anropa scene.pick() utan argument (den siktar på markören), eller använd input.mouseViewport när du behöver siffrorna.

Virtuella kontroller är i canvas-pixlar#

input.addVirtualJoystick(id, x, y, size) och input.addVirtualButton(...) placerar sina mittpunkter i samma rymd som mouseViewport. En joystick returnerar ett normaliserat X/Y efter sin radiella dödzon; en knapp exponerar held, pressed-this-frame och released-this-frame separat. Deras pekare fångas och konsumeras, så att en beröring på kontrollen inte också rapporteras som en rå canvas-åtgärd.

Standardjoysticken är enhetlig. input.getJoystickX/Y() väljer en komplett vektor från styrspaken på skärmen, en fysisk handkontroll, eller tangentbordet (WASD/piltangenter), vilken källa som än är starkast. En overksam ansluten handkontroll inaktiverar därför inte tangentbordsrörelse. Dess Y-axel följer 2D-skärmrymden: upp är -1.

Tre sätt att skapa ett objekt, och de är inte utbytbara#

Vad det skaparLever i
scene.createEntity(def)en författad entitet: den syns i Scene Graph och Inspectorprojektet, den sparas
game.spawn(type, pos)en naken primitiv: ingen hälsa, ingen AI, inget beteendespelsessionen
game.spawnFrom(template, pos, opts)en kopia av en hel entitet: komponenter, fysikkropp, taggar, lager, skala och den kompilerade beteendestackenspelsessionen

game.spawn är rätt för skräp och prototyper och oanvändbart för fiender: en spawner matad av den producerar livlösa bollar. Mallen spawnFrom kopierar är en vanlig entitet du sätter ihop i editorn och slår av med hierarkins öga: inget separat format, ingen andra editor, så det du ser är det som visas.

// 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 accepterar ett id, ett namn eller en tagg. Kopior gjorda under spelande är play-scoped, så stop() tar bort dem och redigeringsscenen förorenas aldrig.

Att stänga av en entitet betyder dold OCH tyst: noden inaktiverad, skriptet stoppat, fysikkroppen svarar inte längre. Att bara dölja skulle lämna en osynlig fiende som fortfarande träffar dig. game.setEnabled(id, on) styr det från ett skript, ögat styr det från hierarkin.

Att flytta något: knuffa en kropp, teleportera inte#

Att skriva transform.position varje bildruta går genom väggar, genom andra monster och genom golvet. Styr hastigheten i stället, men bara när det finns något att styra:

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 utan en kropp fyller ett fält ingen integrerar, och entiteten stannar dödstopp. Det är varför physics.hasBody finns, och varför varje rörelsebeteende i katalogen ställer frågan i stället för att anta ett svar. Att stanna betyder att avbryta hastigheten: att inte göra något lämnar kroppen glidande.

Att skriva en position rör nu verkligen en fysikkropp. Havok styr transformen, aldrig tvärtom, så att tilldela transform.position på en dynamisk kropp brukade skrivas över vid nästa steg: varje teleport misslyckades tyst på en fysisk entitet. Positionsproxyn synkar nu om kroppen, på samma sätt som rotationsproxyn alltid har gjort.

Pathfinding är en ögonblicksbild#

pathfinding.createGrid() skickar ut strålar över ett rutnät en gång, centrerat där det anropades. Att lämna den lådan returnerar null, och de flesta anropare faller då tillbaka på en rak linje, genom väggar, utan ett ord.

Rutnätet centrerar om sig på den begärda resan som standard. setAutoRecenter(false) väljer bort det på en fast värld, där ombyggnad kostar ett strålpass för ingenting.

console.log loggar. Print ritar.#

Konsolen är console.log / warn / error, som var som helst annars. BASIC:s Print ritar på skärmen och anropas varje bildruta, de två är medvetet separata kommandon, och att blanda ihop dem är vad som fyller en konsol med en bildruteloop.

Var ett skript körs#

I editorn och i ett webbygge körs skript i webbläsaren. I ett native skrivbords- eller mobilbygge körs de oförändrade på en inbäddad QuickJS-runtime, samma källa, ingen portning.

Skript lagras i webbläsarens egen databas medan du redigerar, så att en osparad buffert överlever en omladdning; de sparas in i projektet med allt annat.

Begränsningar och vanliga fallgropar#

  • scene.pick() med ett argument vill ha canvas-koordinater. Se ovan; det är den enskilt vanligaste fel-plats-buggen.
  • game.spawn skapar en primitiv, inte en karaktär. Använd spawnFrom.
  • En kropp skapas för entiteter födda mitt i spelet. Fysik brukade byggas bara vid Play, så allt som spawnades efteråt föll tyst genom världen medan dess handplacerade grannar betedde sig normalt.
  • Åtta steg är AI-lagkamratens tak, inte ditt. En skriptloop har ingen sådan gräns; en skenande while kommer att hänga bildrutan som var som helst annars.
  • De andra fyra språkvyerna transpileras från den här. Att redigera en Python- eller Rust-vy och byta tillbaka går genom den lagrade JavaScript- koden, se BASIC-sidan för varför.
</content>