JavaScript in the Engine

Enginen skripti-API: elinkaari, koordinaattiansat, kolme tapaa luoda objekti, ja miten liikutat sitä kävelemättä seinien läpi.

Yhdellä lauseella#

Jokainen entiteetti voi kantaa JavaScript-skriptiä. Se toimii selaimessa, sinun välilehdessäsi, elävää kohtausta vasten, ja se on skriptaus-API:n kanoninen muoto: BASIC-murre, sekä editorin Python-, C#- ja Rust-näkymät, kääntyvät kaikki samaksi rajapinnaksi.

Tyhjentävä lista on API-viite: 538 jäsentä 50 nimiavaruudessa, oma sivunsa kullekin, hakutoiminnolla. Tämä sivu kertoo sen, mitä sinun täytyy tietää ennen sen lukemista.

Elinkaari#

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 ja entity ovat globaaleja; getComponent(type) lukee komponentin siltä entiteetiltä, joka omistaa skriptin.

Törmäykset ja liipaisimet laukeavat sekä 2D:ssä että 3D:ssä, ja kumpikin puoli saa oman kosketusnormaalinsa: other.contact.normal osoittaa toisesta entiteetistä kohti tätä. Juuri se antaa skriptin erottaa "minä laskeuduin sen päälle" ja "se laskeutui minun päälleni": tasohyppelyn tallonta lukee other.contact.normal.y > 0.5.

Kaksi koordinaattiansaa#

Molemmat maksavat oikeaa debug-aikaa, eivätkä kumpikaan epäonnistu äänekkäästi.

Hiiri: sivu versus canvas#

input.mousePosition on sivun koordinaateissa. scene.pick() haluaa canvasin koordinaatit. Toisen antaminen toisen sijaan ei osu ohi, se osuu pisteeseen, joka on siirtynyt näyttöikkunan sijainnin verran ikkunassa, joten napsauta-liikkuaksesi-hahmo kävelee jonnekin uskottavan näköiseen mutta väärään paikkaan.

Kutsu scene.pick() ilman argumenttia (se tähtää kohdistimeen), tai käytä input.mouseViewport-arvoa, kun tarvitset luvut.

Virtuaaliset ohjaimet ovat canvas-pikseleissä#

input.addVirtualJoystick(id, x, y, size) ja input.addVirtualButton(...) sijoittavat keskipisteensä samaan tilaan kuin mouseViewport. Joystick palauttaa normalisoidun X/Y:n sen radiaalisen kuolleen alueen jälkeen; nappi paljastaa held, pressed-this-frame ja released-this-frame erikseen. Niiden osoitin kaapataan ja kulutetaan, joten kosketus ohjaimeen ei raportoidu myös raakana canvas-toimintona.

Oletusjoystick on yhtenäinen. input.getJoystickX/Y() poimii yhden täydellisen vektorin ruudulla olevasta sauvasta, fyysisestä ohjaimesta, tai näppäimistöstä (WASD/nuolet), kumpi lähde onkin vahvin. Joutilas kytketty ohjain ei siis kytke näppäimistöliikettä pois päältä. Sen Y-akseli seuraa 2D-näyttötilaa: ylös on -1.

Kolme tapaa luoda objekti, eivätkä ne ole keskenään vaihdettavissa#

Mitä se luoElää
scene.createEntity(def)auktorisoidun entiteetin: se näkyy Scene Graphissa ja Inspectorissaprojektin ajan, se tallennetaan
game.spawn(type, pos)paljaan primitiivin: ei terveyttä, ei AI:ta, ei käytöstäpelisession ajan
game.spawnFrom(template, pos, opts)kopion kokonaisesta entiteetistä: komponentit, fysiikkakappale, tagit, kerros, skaala ja käännetty käytöspinopelisession ajan

game.spawn on oikea valinta roskalle ja prototyypeille ja hyödytön vihollisille: sillä ruokittu spawner tuottaa elottomia palloja. Malli, jonka spawnFrom kopioi, on tavallinen entiteetti, jonka sävellät editorissa ja kytket pois hierarkian silmällä: ei erillistä formaattia, ei toista editoria, joten se, minkä näet, on se, mikä ilmestyy.

// 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 hyväksyy id:n, nimen tai tagin. Pelin aikana tehdyt kopiot ovat peli-scope: stop() poistaa ne, eikä muokkauskohtaus koskaan saastu.

Entiteetin kytkeminen pois päältä tarkoittaa piiloon JA hiljaiseksi: solmu poissa käytöstä, skripti pysäytetty, fysiikkakappale ei enää vastaa. Pelkkä piilottaminen jättäisi näkymättömän vihollisen yhä osumaan sinuun. game.setEnabled(id, on) ohjaa sitä skriptistä, silmäikoni ohjaa sitä hierarkiasta.

Jonkin liikuttaminen: työnnä kappaletta, älä teleporttaa#

transform.position-arvon kirjoittaminen joka ruudulla kävelee seinien, muiden hirviöiden ja lattian läpi. Ohjaa sen sijaan nopeutta, mutta vain kun jotain on ohjattavana:

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 ilman kappaletta täyttää kentän, jota kukaan ei integroi, ja entiteetti pysähtyy täysin. Siksi physics.hasBody on olemassa, ja siksi jokainen luettelon liikekäytös kysyy kysymyksen sen sijaan, että olettaisi vastauksen. Pysähtyminen tarkoittaa nopeuden peruuttamista: tekemättä jättäminen jättää kappaleen liukumaan.

Sijainnin kirjoittaminen liikuttaa nyt oikeasti fysiikkakappaletta. Havok ohjaa transformia, ei koskaan toisin päin, joten transform.position- arvon asettaminen dynaamiselle kappaleelle ylikirjoitettiin ennen seuraavassa askeleessa: jokainen teleporttaus epäonnistui hiljaa fyysisellä entiteetillä. Sijaintiproksi synkronoi kappaleen uudelleen, samaan tapaan kuin rotaatioproksi on aina tehnyt.

Reitinhaku on tilannekuva#

pathfinding.createGrid() raycastaa ruudukon kerran, keskitettynä kutsuhetken kohtaan. Sen laatikosta poistuminen palauttaa nullin, ja useimmat kutsujat turvautuvat sitten suoraan viivaan, seinien läpi, sanaakaan sanomatta.

Ruudukko keskittyy uudelleen jokaisella pyydetyllä matkalla oletuksena. setAutoRecenter(false) kieltäytyy tästä kiinteässä maailmassa, jossa uudelleenrakentaminen maksaisi raycast-läpikäynnin turhaan.

console.log lokittaa. Print piirtää.#

Konsoli on console.log / warn / error, kuten missä tahansa muualla. BASIC-kielen Print piirtää ruudulle ja sitä kutsutaan joka ruudulla, nämä kaksi ovat tarkoituksella erilliset komennot, ja niiden sekoittaminen on se, mikä täyttää konsolin ruutuluupilla.

Missä skripti toimii#

Editorissa ja web-buildissa skriptit toimivat selaimessa. Natiivissa työpöytä- tai mobiilibuildissa ne toimivat muuttumattomana upotetulla QuickJS-ajonaikaisella ympäristöllä, samasta lähteestä, ei porttina.

Skriptit tallennetaan selaimen omaan tietokantaan muokkauksen ajan, joten tallentamaton puskuri selviytyy uudelleenlatauksesta; ne tallennetaan projektiin kaiken muun mukana.

Rajoitukset ja yleiset sudenkuopat#

  • scene.pick() argumentin kanssa haluaa canvas-koordinaatit. Katso yllä; se on yleisin väärä-paikka-bugi.
  • game.spawn tekee primitiivin, ei hahmoa. Käytä spawnFromia.
  • Kappale luodaan pelin aikana syntyneille entiteeteille. Fysiikka rakennettiin ennen vain Play-hetkellä, joten mikä tahansa sen jälkeen synnytetty putosi maailman läpi hiljaa, kun sen käsin sijoitetut naapurit käyttäytyivät normaalisti.
  • Kahdeksan askelta on AI-tiimikaverin katto, ei sinun. Skriptiluupilla ei ole tällaista rajaa; karkaava while jumittaa ruudun samalla tavalla kuin missä tahansa muuallakin.
  • Neljä muuta kielinäkymää on transpiloitu tästä. Python- tai Rust- näkymän muokkaaminen ja takaisin vaihtaminen kulkee tallennetun JavaScriptin kautta, katso BASIC-sivu, miksi näin on.