JavaScript in der Engine
Die Skript-API der Engine, der Lifecycle, die Koordinatenfallen, die drei Wege, ein Objekt zu erzeugen, und wie man eines bewegt, ohne durch Wände zu laufen.
In einem Satz#
Jede Entity kann ein JavaScript-Skript tragen. Es läuft im Browser, in Ihrem Tab, gegen die lebende Szene, und es ist die kanonische Scripting-API: Der BASIC-Dialekt und die Python-, C#- und Rust-Ansichten im Editor kompilieren alle auf dieselbe Oberfläche herunter.
Die vollständige Liste ist die API-Referenz: 538 Members in 50 Namespaces, je eine Seite, mit einer Suche. Diese Seite ist das, was Sie wissen müssen, bevor Sie sie lesen.
Der Lifecycle#
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 und entity sind Globals; getComponent(type)
liest eine Component von der Entity, der das Skript gehört.
Collisions und Triggers feuern sowohl in 2D als auch in 3D, und jede Seite
erhält ihre eigene Contact Normal: other.contact.normal zeigt von der
anderen Entity zu dieser hier. Das erlaubt einem Skript, „ich bin darauf
gelandet" von „es ist auf mir gelandet" zu unterscheiden, ein
Platformer-Stomp liest other.contact.normal.y > 0.5.
Die zwei Koordinatenfallen#
Beide kosten echte Debugging-Zeit, und keine schlägt laut fehl.
Maus: Page versus Canvas#
input.mousePosition liegt in page-Koordinaten. scene.pick() erwartet
canvas-Koordinaten. Das eine für das andere zu übergeben, geht nicht
einfach daneben, es trifft einen Punkt, der um die Position des Viewports im
Fenster versetzt ist, sodass ein Click-to-Move-Held irgendwo plausibel
Falsches hinläuft.
Rufen Sie scene.pick() ohne Argument auf (es zielt auf den Cursor), oder
verwenden Sie input.mouseViewport, wenn Sie die Zahlen brauchen.
Virtuelle Controls liegen in Canvas-Pixeln#
input.addVirtualJoystick(id, x, y, size) und input.addVirtualButton(...)
platzieren ihre Mittelpunkte im selben Raum wie mouseViewport. Ein Joystick
liefert ein normalisiertes X/Y nach seiner radialen Dead Zone; ein Button legt
held, pressed-this-frame und released-this-frame getrennt offen. Ihr
Pointer wird eingefangen und verbraucht, sodass eine Berührung des Controls
nicht zusätzlich als rohe Canvas-Aktion gemeldet wird.
Der Standard-Joystick ist vereinheitlicht.
input.getJoystickX/Y()wählt einen vollständigen Vektor vom Onscreen-Stick, einem physischen Gamepad, oder der Tastatur (WASD/Pfeiltasten), je nachdem, welche Quelle am stärksten ist. Ein untätiges angeschlossenes Pad deaktiviert daher nicht die Tastatursteuerung. Seine Y-Achse folgt dem 2D-Bildschirmraum: oben ist-1.
Drei Wege, ein Objekt zu erzeugen, und sie sind nicht austauschbar#
| Was es erzeugt | Lebt für | |
|---|---|---|
scene.createEntity(def) | eine autorierte Entity: sie erscheint im Scene Graph und im Inspector | das Projekt, sie wird gespeichert |
game.spawn(type, pos) | ein nacktes Primitiv: keine Health, keine KI, kein Verhalten | die Play-Session |
game.spawnFrom(template, pos, opts) | eine Kopie einer ganzen Entity: Components, Physics Body, Tags, Layer, Scale und der kompilierte Behaviour-Stack | die Play-Session |
game.spawn ist richtig für Trümmer und Prototypen und nutzlos für
Gegner: ein damit gefütterter Spawner erzeugt reglose Bälle. Das Template,
das spawnFrom kopiert, ist eine gewöhnliche Entity, die Sie im Editor
zusammenstellen und mit dem Augensymbol der Hierarchie ausschalten: kein
separates Format, kein zweiter Editor, sodass das, was Sie sehen, auch das
ist, was erscheint.
// 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 akzeptiert eine id, einen Namen oder einen Tag. Während des Spiels
erzeugte Kopien sind play-scoped, stop() entfernt sie also, und die
Edit-Szene wird nie verschmutzt.
Eine Entity auszuschalten bedeutet versteckt UND stumm: Node deaktiviert, Skript gestoppt, Physics Body reagiert nicht mehr. Nur Verstecken würde einen unsichtbaren Gegner übrig lassen, der Sie weiterhin trifft.
game.setEnabled(id, on)steuert das aus einem Skript, das Augensymbol steuert es aus der Hierarchie.
Etwas bewegen: einen Body schieben, nicht teleportieren#
Jeden Frame transform.position zu schreiben, läuft durch Wände, durch
andere Monster und durch den Boden. Steuern Sie stattdessen die
velocity, aber nur, wenn es etwas zu steuern gibt:
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 ohne Body füllt ein Feld, das niemand integriert, und
die Entity bleibt tot stehen. Deshalb existiert physics.hasBody, und
deshalb stellt jedes Bewegungsverhalten im Katalog die Frage, statt eine
Antwort anzunehmen. Stoppen bedeutet, die Velocity zu annullieren: Nichts
zu tun lässt den Body weiterrollen.
Eine Position zu schreiben, bewegt jetzt auch einen Physics Body. Havok steuert den Transform, nie umgekehrt, sodass eine Zuweisung an
transform.positionauf einem dynamischen Body früher im nächsten Schritt überschrieben wurde: Jedes Teleport schlug bei einer physischen Entity stillschweigend fehl. Der Position-Proxy synchronisiert den Body neu, so wie es der Rotation-Proxy schon immer tat.
Pathfinding ist ein Snapshot#
pathfinding.createGrid() tastet ein Grid einmal per Raycast ab,
zentriert dort, wo es aufgerufen wurde. Diese Box zu verlassen, gibt null
zurück, und die meisten Aufrufer fallen dann kommentarlos auf eine gerade
Linie zurück, durch Wände hindurch.
Das Grid zentriert sich standardmäßig bei jeder angeforderten Route neu.
setAutoRecenter(false) schaltet das für eine feste Welt ab, wo ein
Neuaufbau einen Raycast-Durchgang umsonst kostet.
console.log protokolliert. Print zeichnet.#
Die Konsole ist console.log / warn / error, wie überall sonst. Das
BASIC-Print zeichnet auf den Bildschirm und wird jeden Frame aufgerufen,
die beiden sind absichtlich getrennte Befehle, und
sie zu verwechseln, ist das, was eine Konsole mit einer Frame-Loop füllt.
Wo ein Skript läuft#
Im Editor und in einem Web-Build laufen Skripte im Browser. In einem nativen Desktop- oder Mobile-Build laufen sie unverändert auf einer eingebetteten QuickJS-Runtime, derselbe Quellcode, kein Port.
Skripte werden während der Bearbeitung in der eigenen Datenbank des Browsers gespeichert, sodass ein ungespeicherter Puffer einen Reload übersteht; sie werden mit allem anderen ins Projekt gespeichert.
Grenzen und typische Stolperfallen#
scene.pick()mit einem Argument erwartet Canvas-Koordinaten. Siehe oben; das ist der mit Abstand häufigste Wrong-Place-Bug.game.spawnerzeugt ein Primitiv, keine Figur. Verwenden SiespawnFrom.- Für Entities, die mitten im Spiel entstehen, wird ein Body erzeugt. Physics wurde früher nur beim Play-Start gebaut, sodass alles, was danach gespawnt wurde, stillschweigend durch die Welt fiel, während seine von Hand platzierten Nachbarn sich normal verhielten.
- Acht Schritte sind die Obergrenze des KI-Teammitglieds, nicht Ihre. Eine
Skript-Loop hat kein solches Limit; ein außer Kontrolle geratenes
whilehängt den Frame wie überall sonst. - Die anderen vier Sprachansichten werden aus dieser hier transpiliert. Eine Python- oder Rust-Ansicht zu bearbeiten und zurückzuwechseln, geht über das gespeicherte JavaScript, siehe die BASIC-Seite für den Grund.