JavaScript en el Engine
La API de scripting del Engine: el ciclo de vida, las trampas de coordenadas, las tres formas de crear un objeto, y cómo mover uno sin atravesar paredes.
En una frase#
Cada entidad puede llevar un script en JavaScript. Se ejecuta en el navegador, en tu propia pestaña, contra la escena en vivo, y es la API de scripting canónica: el dialecto BASIC, y las vistas Python, C# y Rust del editor, todas compilan hacia la misma superficie.
La lista exhaustiva es la referencia de la API: 538 miembros en 50 espacios de nombres, una página cada uno, con buscador. Esta página es lo que necesitas saber antes de leer aquella.
El ciclo de vida#
function start() {} // una vez, cuando arranca la entidad
function update(dt) {} // cada fotograma, dt en segundos
function fixedUpdate(dt) {} // paso fijo, para física
function onCollisionEnter(other) {} // primer fotograma de contacto
function onCollisionStay(other) {} // cada fotograma mientras se tocan
function onCollisionExit(other) {} // el contacto acaba de terminar
function onTriggerEnter(other) {} // entró en un volumen trigger
function onTriggerExit(other) {}
function onMessage(msg, data, senderId) {} // mensajería entidad a entidad
function onAnimationEvent(name, data) {}
function onDestroy() {}
function main() {} // entrada para juego de consola, async permitido
deltaTime, time, entityId y entity son globales; getComponent(type)
lee un componente de la entidad que posee el script.
Las colisiones y los triggers se disparan tanto en 2D como en 3D, y cada
lado recibe su propia normal de contacto: other.contact.normal apunta desde
la otra entidad hacia esta. Eso es lo que le permite a un script distinguir
"aterricé sobre él" de "él aterrizó sobre mí", un stomp de plataformas lee
other.contact.normal.y > 0.5.
Las dos trampas de coordenadas#
Ambas cuestan tiempo real de depuración, y ninguna falla de forma evidente.
El ratón: página frente a lienzo#
input.mousePosition está en coordenadas de página. scene.pick() quiere
coordenadas de canvas. Pasar una en lugar de la otra no falla del todo,
golpea un punto desplazado por la posición del viewport en la ventana, así que
un click-to-move del héroe camina hacia algún sitio verosímil y equivocado.
Llama a scene.pick() sin argumento (apunta al cursor), o usa
input.mouseViewport cuando necesites los números.
Los controles virtuales están en píxeles de canvas#
input.addVirtualJoystick(id, x, y, size) y input.addVirtualButton(...)
colocan sus centros en el mismo espacio que mouseViewport. Un joystick
devuelve un vector X/Y normalizado tras su zona muerta radial; un botón expone
held, pressed-this-frame y released-this-frame por separado. Su puntero
queda capturado y consumido, así que un toque sobre el control no se reporta
también como una acción de canvas en bruto.
El joystick por defecto está unificado.
input.getJoystickX/Y()elige un vector completo de la fuente más fuerte entre el stick en pantalla, un mando físico, o el teclado (WASD/flechas). Un mando conectado pero inactivo no desactiva por tanto el movimiento por teclado. Su eje Y sigue el espacio de pantalla 2D: arriba es-1.
Tres formas de crear un objeto, y no son intercambiables#
| Qué crea | Vive mientras | |
|---|---|---|
scene.createEntity(def) | una entidad autorada: aparece en el Scene Graph y en el Inspector | dure el proyecto, se guarda |
game.spawn(type, pos) | una primitiva desnuda: sin salud, sin IA, sin comportamiento | dure la sesión de juego |
game.spawnFrom(template, pos, opts) | una copia de una entidad completa: componentes, cuerpo físico, tags, layer, escala y la pila de comportamiento compilada | dure la sesión de juego |
game.spawn es correcto para escombros y prototipos y inútil para
enemigos: un spawner alimentado con él produce bolas inertes. El template
que copia spawnFrom es una entidad ordinaria que compones en el editor y
apagas con el ojo de la jerarquía: sin formato aparte, sin segundo editor, así
que lo que ves es lo que aparece.
// Una vez, al inicio: a un template que falta hay que avisar, no fallar cada oleada en silencio
if (!game.hasTemplate('Skeleton')) console.warn('no Skeleton template');
const mob = game.spawnFrom('Skeleton', { x, y, z }, { tag: 'Enemy' });
template acepta un id, un nombre o un tag. Las copias hechas durante el juego
son de alcance de sesión, así que stop() las elimina y la escena de edición
nunca se contamina.
Apagar una entidad significa oculta Y silenciosa: nodo desactivado, script detenido, cuerpo físico que ya no responde. Solo ocultarla dejaría un enemigo invisible que sigue golpeándote.
game.setEnabled(id, on)lo maneja desde un script, el icono del ojo lo maneja desde la jerarquía.
Mover algo: empuja un cuerpo, no lo teletransportes#
Escribir transform.position cada fotograma atraviesa paredes, otros
monstruos y el suelo. Maneja la velocity en su lugar, pero solo cuando hay
algo que manejar:
if (physics.hasBody) {
const v = physics.getVelocity();
physics.setVelocity({ x: vx, y: v.y, z: vz }); // empuja un cuerpo real
} else {
transform.position.x += vx * dt; // legítimo para algo que vuela
}
physics.setVelocity sin un cuerpo rellena un campo que nadie integra, y la
entidad se detiene en seco. Por eso existe physics.hasBody, y por eso todo
comportamiento de movimiento del catálogo hace la pregunta en lugar de asumir
una respuesta. Detenerse significa cancelar la velocity: no hacer nada deja
al cuerpo deslizándose.
Escribir una posición mueve un cuerpo físico, ahora. Havok maneja el transform, nunca al revés, así que asignar
transform.positionsobre un cuerpo dinámico se sobrescribía en el siguiente paso: cada teletransporte fallaba en silencio sobre una entidad física. El proxy de posición resincroniza el cuerpo, como siempre lo hizo el proxy de rotación.
El pathfinding es una instantánea#
pathfinding.createGrid() lanza rayos sobre una cuadrícula una vez,
centrada donde se llamó. Salir de esa caja devuelve null, y la mayoría de
quienes la llaman entonces recurren a una línea recta, atravesando paredes, sin
avisar.
La cuadrícula se recentra en el siguiente viaje solicitado por defecto.
setAutoRecenter(false) permite salir de eso en un mundo fijo, donde
reconstruirla cuesta una pasada de rayos para nada.
console.log registra. Print dibuja.#
La consola es console.log / warn / error, como en cualquier otro sitio.
El Print de BASIC dibuja en la pantalla y se llama cada fotograma, los
dos son comandos deliberadamente separados, y
confundirlos es lo que llena una consola con un bucle de fotogramas.
Dónde se ejecuta un script#
En el editor y en una build web, los scripts se ejecutan en el navegador. En una build de escritorio o móvil nativa, se ejecutan sin cambios sobre un runtime QuickJS embebido, la misma fuente, no un port.
Los scripts se guardan en la propia base de datos del navegador mientras editas, así que un buffer sin guardar sobrevive a una recarga; se guardan en el proyecto junto con todo lo demás.
Límites y problemas frecuentes#
scene.pick()con un argumento quiere coordenadas de canvas. Ver más arriba; es el bug de lugar equivocado más común de todos.game.spawncrea una primitiva, no un personaje. UsaspawnFrom.- Se crea un cuerpo para las entidades nacidas a mitad de partida. La física solía construirse solo al pulsar Play, así que cualquier cosa generada después caía en silencio a través del mundo mientras sus vecinos colocados a mano se comportaban con normalidad.
- Ocho pasos es el techo del compañero de IA, no el tuyo. Un bucle de
script no tiene ese límite; un
whiledescontrolado va a colgar el fotograma como en cualquier otro sitio. - Las otras cuatro vistas de lenguaje se transpilan desde esta. Editar una vista Python o Rust y volver atrás pasa por el JavaScript guardado, ver la página de BASIC para saber por qué.