JavaScript in de Engine

De script-API van de Engine: de levenscyclus, de coördinatenvallen, de drie manieren om een object te maken, en hoe je er één beweegt zonder door muren te lopen.

In het kort#

Elke entiteit kan een JavaScript-script dragen. Het draait in de browser, in uw tabblad, tegen de levende scène, en het is de canonieke scripting-API: het BASIC-dialect, en de Python-, C#- en Rust-weergaven in de editor, compileren allemaal naar hetzelfde oppervlak.

De volledige lijst is de API-referentie: 538 leden in 50 namespaces, elk met een eigen pagina en een zoekfunctie. Deze pagina is wat u moet weten voordat u die leest.

De levenscyclus#

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 en entity zijn globalen; getComponent(type) leest een component van de entiteit die het script bezit.

Botsingen en triggers vuren zowel in 2D als in 3D, en elke kant ontvangt zijn eigen contactnormaal: other.contact.normal wijst van de andere entiteit naar deze. Dat is wat een script "ik ben erop geland" laat onderscheiden van "het is op mij geland", een platformer-stomp leest other.contact.normal.y > 0.5.

De twee coördinatenvallen#

Beide kosten echte debugtijd, en geen van beide faalt luid.

Muis: pagina versus canvas#

input.mousePosition staat in paginacoördinaten. scene.pick() wil canvascoördinaten. De ene voor de andere doorgeven mist niet, het raakt een punt dat verschoven is met de positie van de viewport in het venster, zodat een klik-om-te-bewegen-held ergens aannemelijks en verkeerds heen loopt.

Roep scene.pick() aan zonder argument (het richt zich op de cursor), of gebruik input.mouseViewport wanneer u de getallen nodig hebt.

Virtuele bedieningselementen staan in canvaspixels#

input.addVirtualJoystick(id, x, y, size) en input.addVirtualButton(...) plaatsen hun middelpunten in dezelfde ruimte als mouseViewport. Een joystick geeft een genormaliseerde X/Y terug na zijn radiale dode zone; een knop geeft held, pressed-this-frame en released-this-frame apart terug. Hun pointer wordt vastgehouden en verbruikt, zodat een aanraking op het bedieningselement niet ook als een ruwe canvasactie wordt gemeld.

De standaardjoystick is verenigd. input.getJoystickX/Y() kiest één volledige vector van de joystick op het scherm, een fysieke gamepad, of het toetsenbord (WASD/pijltoetsen), afhankelijk van welke bron het sterkst is. Een inactieve verbonden pad schakelt de toetsenbordbeweging dus niet uit. De Y-as volgt de 2D-schermruimte: op is -1.

Drie manieren om een object te maken, en ze zijn niet inwisselbaar#

Wat het maaktLeeft voor
scene.createEntity(def)een geautoriseerde entiteit: ze verschijnt in de Scene Graph en de Inspectorhet project, ze wordt opgeslagen
game.spawn(type, pos)een kale primitieve: geen leven, geen AI, geen gedragde speelsessie
game.spawnFrom(template, pos, opts)een kopie van een volledige entiteit: componenten, fysiek lichaam, tags, laag, schaal en de gecompileerde gedragsstapelde speelsessie

game.spawn is geschikt voor puin en prototypes en nutteloos voor vijanden: een spawner die ermee gevoed wordt, produceert levenloze ballen. De template die spawnFrom kopieert, is een gewone entiteit die u in de editor samenstelt en uitschakelt met het oog van de hiërarchie: geen apart formaat, geen tweede editor, dus wat u ziet is wat verschijnt.

// 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 aanvaardt een id, een naam of een tag. Kopieën die tijdens het spelen worden gemaakt, zijn beperkt tot de speelsessie, dus stop() verwijdert ze en de bewerkingsscène raakt nooit vervuild.

Een entiteit uitschakelen betekent verborgen ÉN stil: node uitgeschakeld, script gestopt, fysiek lichaam reageert niet meer. Alleen verbergen zou een onzichtbare vijand achterlaten die u nog altijd raakt. game.setEnabled(id, on) stuurt dit aan vanuit een script, het oogpictogram stuurt het aan vanuit de hiërarchie.

Iets bewegen: duw een lichaam, teleporteer niet#

transform.position elke frame beschrijven loopt door muren, door andere monsters en door de vloer. Stuur in plaats daarvan de snelheid aan, maar alleen wanneer er iets is om aan te sturen:

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 zonder lichaam vult een veld dat niemand integreert, en de entiteit stopt dood. Daarom bestaat physics.hasBody, en daarom stelt elk bewegend gedrag in de catalogus de vraag in plaats van een antwoord aan te nemen. Stoppen betekent de snelheid annuleren: niets doen laat het lichaam doorglijden.

Een positie schrijven verplaatst nu wel degelijk een fysiek lichaam. Havok stuurt de transform aan, nooit andersom, dus transform.position toewijzen aan een dynamisch lichaam werd vroeger overschreven bij de volgende stap: elke teleportatie mislukte stilzwijgend op een fysieke entiteit. De positieproxy synchroniseert het lichaam opnieuw, zoals de rotatieproxy dat altijd al deed.

Pathfinding is een momentopname#

pathfinding.createGrid() schiet eenmalig stralen door een raster, gecentreerd waar het werd aangeroepen. Dat kader verlaten geeft null terug, en de meeste aanroepers vallen dan terug op een rechte lijn, door muren heen, zonder een woord.

Het raster centreert zichzelf standaard opnieuw bij de gevraagde reis. setAutoRecenter(false) schakelt dit uit op een vaste wereld, waar herbouwen voor niets een stralenpassage kost.

console.log logt. Print tekent.#

De console is console.log / warn / error, zoals overal elders. De BASIC Print tekent op het scherm en wordt elke frame aangeroepen, de twee zijn bewust gescheiden commando's, en ze door elkaar halen is wat een console vult met een framelus.

Waar een script draait#

In de editor en in een webbuild draaien scripts in de browser. In een native desktop- of mobiele build draaien ze ongewijzigd op een ingebedde QuickJS-runtime, dezelfde bron, geen port.

Scripts worden opgeslagen in de eigen database van de browser terwijl u bewerkt, zodat een niet-opgeslagen buffer een herlaadbeurt overleeft; ze worden met al het andere in het project opgeslagen.

Grenzen en veelvoorkomende valkuilen#

  • scene.pick() met een argument wil canvascoördinaten. Zie hierboven; het is de meest voorkomende bug op de verkeerde plaats.
  • game.spawn maakt een primitieve, geen personage. Gebruik spawnFrom.
  • Er wordt een lichaam gemaakt voor entiteiten die midden in het spel geboren worden. Fysica werd vroeger alleen gebouwd bij Play, dus alles wat daarna werd gespawned, viel stilzwijgend door de wereld terwijl zijn handmatig geplaatste buren zich gedroegen.
  • Acht stappen is het plafond van de AI-teamgenoot, niet het uwe. Een scriptlus kent zo'n grens niet; een op hol geslagen while zal de frame laten hangen zoals overal elders.
  • De andere vier taalweergaven zijn getranspileerd uit deze. Een Python- of Rust-weergave bewerken en terugschakelen gaat via de opgeslagen JavaScript, zie de BASIC-pagina voor waarom.