La API de ctx
ctx es tu asa sobre la escena en marcha: se pasa a init y todo lo de abajo cuelga de él.
Es deliberadamente pequeño. Si buscas algo y no lo encuentras aquí, hay buenas probabilidades de que la respuesta sea un paso integrado y no una API.
Dónde estás
ctx.entity | el objeto al que está adjunto este script |
ctx.scene | la escena a la que pertenece |
ctx.space | el mundo |
Ciclo de vida y eventos
ctx.tick((dt, t) => {}); // en cada fotograma; dt y t en segundos
ctx.effect(() => {}); // se reejecuta cuando cambia lo que lee; puede devolver una limpieza
ctx.on(trigger, (payload) => {}); // suscribirse a un disparador
ctx.emit(trigger, payload); // lanzar uno desde este objeto
Los disparadores usan los mismos nombres que los eventos y los patches. Los que traen información útil:
| Disparador | Recibes |
|---|---|
on-keydown · on-keyup | { code, ctrl, shift, alt, meta } |
on-state-active · on-state-inactive | { stateId } |
on-collide | { other } — contra qué chocaste |
on-divkit-action | { id, … } — qué botón |
on-game-control | { state } — quieto, moviéndose, corriendo o saltando |
on-drag · on-pinch · on-rotate | { dx, dy } · { scale } · { angle } |
on-vps-localized | dónde resultó estar el visitante |
on-launch llega en el momento en que se crea tu instancia, así que no puedes perdértelo por
arrancar tarde.
Encontrar objetos
ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // el primer objeto con estos componentes
ctx.query(LightComponent, TagsComponent); // todos ellos
ctx.all();
Crear y eliminar
ctx.create({ name: 'Bullet', parent, components: [] });
ctx.spawn(props.bulletModel, { parent });
ctx.destroy(entity);
spawn es el cómodo: dale un recurso y monta los componentes adecuados. Un modelo se convierte en
un objeto de modelo, una imagen en un plano texturizado, un sonido en una fuente de audio.
Ejecutar comportamiento integrado
ctx.step('play_animation', { presetId }, { targets: [enemy] });
ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// los cambios hechos aquí se interpolan en vez de saltar
});
ctx.step ejecuta cualquiera de los pasos integrados, los mismos
que usan tus eventos. Animación, cambio de state, transiciones de escena y transiciones están a una
llamada, así que casi nunca necesitas reimplementarlos.
Navegación
ctx.openScene(sceneOrId);
await ctx.openSpace(spaceRefOrId);
ctx.scenes();
Entrada
Dos capas, para dos trabajos distintos.
Las acciones con nombre leen las asignaciones de teclas del proyecto, así que a un visitante que se remapee las teclas se le respeta:
ctx.input.pressed('jump');
ctx.input.justPressed('fire');
ctx.input.axis('moveX');
Las teclas en crudo leen el teclado directamente. Una pulsación dura exactamente un fotograma,
así que léelas dentro de ctx.tick:
ctx.keyboard.down('KeyW');
ctx.keyboard.press('Space');
ctx.keyboard.press('ArrowLeft', { every: 200 }); // repetición automática, en ms
ctx.keyboard.axis('KeyA', 'KeyD'); // -1, 0 o 1
Una asignación con nombre ignora los modificadores que no menciona: esprintar con Shift pulsado no debe cancelar «adelante». Un disparador de tecla es lo contrario: un modificador que no marcaste significa «no debe estar pulsado».
Perder el foco de la ventana suelta las teclas pulsadas, así que nada se queda atascado.
Cámara
ctx.camera.entity(); // el objeto de la cámara activa
ctx.camera.setActive(target); // cambiar de cámara; null restaura la de por defecto
ctx.camera.pose(); // { position, rotation, forward } en coordenadas de mundo
El transform de la cámara es la fuente de verdad en todos los modos de control: escribe en él para mover la cámara, léelo para ver dónde la han puesto los controles. En los modos orbit y primera persona los controles son dueños de la orientación, así que una rotación que escribas será sobrescrita; la posición sí se respeta.
Raycasting
await ctx.raycast(); // desde el centro de la cámara
await ctx.raycast({ screen: { x: 0.5, y: 0 }, all: true }); // un punto de pantalla, todos los impactos
await ctx.raycast({ origin, direction }); // el rayo que quieras
await ctx.raycast({ from: entity }); // desde un objeto, a lo largo de su frente
Cada impacto te dice el objeto, la distancia, y el punto y la normal de la superficie en coordenadas de mundo, ordenados de más cercano a más lejano, un impacto por objeto.
El rayo se lanza contra geometría real en el lado del renderizado, así que la respuesta llega al siguiente fotograma. Los objetos invisibles y los marcados para ignorarse (retículas, gizmos) se saltan, de modo que responde lo que hay detrás en vez de que el rayo informe de un fallo.
Física
ctx.physics.applyImpulse(target, { x: 0, y: 5, z: 0 });
ctx.physics.applyForce(target, vec, point);
ctx.physics.setVelocity(target, vec);
ctx.physics.teleport(target, position, { rotation, keepVelocity });
ctx.physics.setGravity(vec);
ctx.physics.getSpeed(target);
ctx.physics.isSleeping(target);
await ctx.physics.raycast(from, to, { skip: [ctx.entity] });
La física es dueña de su posición. Usa teleport para colocarlo e impulsos o fuerzas para moverlo.
Y pasa skip cuando lances un rayo desde dentro de tu propio cuerpo, o te darás a ti mismo todas
y cada una de las veces.
Las lecturas vienen del último estado sincronizado y van con un fotograma de retraso: bien para «¿me estoy moviendo?», mal para matemáticas instantáneas exactas.
Audio
ctx.audio.play(props.hitSound, { at: enemy, volume: 0.6, positional: true });
Cada llamada arranca un sonido independiente, que es exactamente lo que necesitan los pasos, los impactos y los disparos. El componente de audio es una sola voz y se corta a sí mismo: no lo uses para efectos.
Guardar cosas
Tres almacenes, que se diferencian por su alcance:
// 1. los valores propios de este script
const store = ctx.store('game', { score: { type: 'number', default: 0 } });
store.set('score', (v) => v + 1);
store.subscribe('score', (v) => {});
// 2. globals: compartidos con todos los scripts, patches y eventos
ctx.setGlobal('level', 3);
ctx.getGlobal('level');
ctx.subscribeGlobal('level', (v) => {});
// 3. mensajes entre scripts
ctx.postMessage('enemy-died', { id });
ctx.handleMessage('enemy-died', ({ id }) => {});
| Sobrevive a un cambio de escena | Sobrevive al paso entre spaces | Sobrevive a una recarga | |
|---|---|---|---|
store | sí | no | no |
| globals | sí | sí | no |
| globals de space | sí | no — aislados a propósito | no |
Ninguno de los tres se guarda entre visitas. Si algo debe persistir, mándalo tú a algún sitio mientras todavía tengas conexión.
Interfaz
const ui = ctx.getDivKit(entity);
ui.get('score');
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {});
ui.onAction('restart', () => {});
Consulta Tarjetas de interfaz.
Siguiente: Lo que vas a construir de verdad — scripts completos para copiar.