Aller au contenu principal

L'API ctx

ctx est ce qui permet à votre script de travailler avec la scène en cours. Cette page liste tout ce qu'il sait faire.

init passe ctx à votre script. L'API est volontairement petite. Si vous ne trouvez pas ce que vous cherchez ici, cherchez une étape intégrée qui le fait.

Où vous êtes​

NomCe que c'est
ctx.entityl'objet auquel ce script est attaché
ctx.scenela scène où se trouve l'objet
ctx.spacel'espace — tout le monde dans lequel se trouve le visiteur

Images et déclencheurs​

ctx.tick((dt, t) => {});          // à chaque image ; dt et t en secondes
ctx.effect(() => {}); // se relance quand ce qu'il lit change ; peut renvoyer une fonction de nettoyage
ctx.on(trigger, (payload) => {}); // réagir à un déclencheur
ctx.emit(trigger, payload); // envoyer un déclencheur depuis cet objet

La plupart des déclencheurs portent les mêmes noms que dans les événements. Trois déclencheurs d'entrée à branches arrivent dans un script sous forme de déclencheurs séparés :

  • Key (on-key) arrive sous la forme on-keydown et on-keyup ;
  • Press on the object (on-mouse) arrive sous la forme on-mousedown, on-mouseup et on-click ;
  • Hover (on-hover) arrive sous la forme on-hover et on-blur.

Les déclencheurs liés au temps, à la distance, à la visibilité, aux signaux et aux variables ne fonctionnent que dans les événements. Dans un script, utilisez plutôt ctx.tick, ctx.subscribeGlobal et ctx.handleMessage.

Ces déclencheurs vous donnent des données utiles :

DéclencheurCe que vous recevez
on-keydown · on-keyup{ code, ctrl, shift, alt, meta }
on-state-active · on-state-inactive{ stateId }
on-collide{ other } — ce que vous avez heurté
on-divkit-action{ id, … } — quel bouton
on-game-control{ state } — idle, move, run ou jump
on-drag · on-pinch · on-rotate{ dx, dy } · { scale } · { angle }
on-vps-localizedl'endroit où se trouve réellement le visiteur

on-launch arrive dès que votre script démarre : vous ne pouvez pas le manquer.

Trouver des objets​

ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // le premier objet qui a ces composants
ctx.query(LightComponent, TagsComponent); // tous les objets qui les ont
ctx.all();

Créer et supprimer des objets​

ctx.create({ name: 'Bullet', parent, components: [] });
ctx.spawn(props.bulletModel, { parent });
ctx.destroy(entity);

spawn est le plus simple. Donnez-lui une ressource, et il ajoute pour vous les bons composants :

  • un modèle devient un objet modèle ;
  • une image devient un plan plat qui porte l'image ;
  • un son devient une source audio.

Lancer des étapes intégrées​

ctx.step('set_visibility', { visible: false }, { targets: [enemy] });

ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// les changements faits ici s'animent en douceur au lieu de sauter
});

targets choisit les objets sur lesquels agit l'étape, comme le champ Target object d'un événement. Sans lui, l'étape agit sur l'objet de ce script.

Cherchez une étape avant de l'écrire vous-même

ctx.step lance n'importe laquelle des étapes intégrées, les mêmes que celles de vos événements. Animations, changements d'état et transitions de scène sont à un appel près.

Envoyez le visiteur vers une autre scène ou un autre espace :

ctx.openScene(sceneOrId);
await ctx.openSpace(spaceRefOrId);
ctx.scenes();

Entrées​

Il existe deux façons de lire les entrées.

Les actions nommées utilisent les affectations de touches du projet. Si un visiteur change ses touches, votre script fonctionne toujours :

ctx.input.pressed('jump');
ctx.input.justPressed('fire');
ctx.input.axis('moveX');

Les touches brutes lisent directement le clavier. Un appui dure exactement une image : lisez-le donc dans ctx.tick :

ctx.keyboard.down('KeyW');
ctx.keyboard.press('Space');
ctx.keyboard.press('ArrowLeft', { every: 200 }); // répétition automatique, toutes les 200 ms
ctx.keyboard.axis('KeyA', 'KeyD'); // -1, 0 ou 1
Les touches de modification ne fonctionnent pas pareil dans les deux cas

Une action nommée ignore les touches de modification qu'elle ne mentionne pas : maintenir Shift pour sprinter n'empêche donc pas d'avancer. Un déclencheur de touche fait l'inverse : une touche de modification qui ne fait pas partie de sa touche ne doit pas être enfoncée.

Quand la fenêtre perd le focus, toutes les touches enfoncées sont relâchées : aucune touche ne reste bloquée.

Caméra​

ctx.camera.entity();           // l'objet caméra actif
ctx.camera.setActive(target); // changer de caméra ; null revient à la caméra par défaut
ctx.camera.pose(); // { position, rotation, forward } dans l'espace monde

Le transform de la caméra fonctionne de la même façon dans tous les modes de contrôle. Écrivez-y pour déplacer la caméra. Lisez-le pour savoir où les contrôles ont placé la caméra.

En modes orbit et firstPerson, les contrôles fixent la rotation : une rotation que vous écrivez est donc remplacée. La position que vous écrivez, elle, est conservée.

Lancer de rayons​

Un rayon est une ligne qui part d'un point dans une direction. Lancez-en un pour savoir ce qui se trouve sur son chemin.

await ctx.raycast();                                       // depuis le centre de la caméra
await ctx.raycast({ screen: { x: 0.5, y: 0 }, all: true }); // un point de l'écran, tous les impacts
await ctx.raycast({ origin, direction }); // n'importe quel rayon
await ctx.raycast({ from: entity }); // depuis un objet, dans sa direction avant

Chaque impact vous donne l'objet touché, la distance, ainsi que le point et la normale de la surface dans l'espace monde. Les impacts sont triés du plus proche au plus lointain, un par objet.

Utilisez await

La réponse arrive à l'image suivante.

Les objets invisibles sont ignorés, tout comme les objets marqués pour l'être, comme les réticules et les gizmos. Vous obtenez ce qui se trouve derrière eux, au lieu d'un rayon qui ne touche rien.

Physique​

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] });
Modifier le transform d'un corps dynamique ne fait rien

C'est la physique qui décide où il se trouve. Utilisez teleport pour le placer, et des impulsions ou des forces pour le déplacer.

Ignorez votre propre corps

Quand vous lancez un rayon depuis l'intérieur de votre propre corps, passez skip. Sinon, le rayon touche votre propre corps à chaque fois.

Les valeurs lues ont environ une image de retard. Cela suffit pour savoir « est-ce que je bouge ? », pas pour des calculs précis.

Audio​

ctx.audio.play(props.hitSound.id, { at: enemy, volume: 0.6, positional: true });

play prend l'identifiant du son. Pour un réglage de son défini avec defineProps, c'est props.hitSound.id.

Chaque appel joue son propre son : les pas, les coups et les tirs ne se coupent donc jamais les uns les autres. Le composant audio, lui, ne joue qu'un son à la fois et se coupe lui-même : ne vous en servez pas pour les effets sonores.

Garder et partager des valeurs​

Trois façons de garder des valeurs et de les partager avec d'autres scripts :

// 1. les valeurs propres à ce script
const store = ctx.store('game', { score: { type: 'number', default: 0 } });
store.set('score', (v) => v + 1);
store.subscribe('score', (v) => {});

// 2. variables globales — partagées avec tous les scripts, patches et événements
ctx.setGlobal('level', 3);
ctx.getGlobal('level');
ctx.subscribeGlobal('level', (v) => {});

// 3. messages entre scripts
ctx.postMessage('enemy-died', { id });
ctx.handleMessage('enemy-died', ({ id }) => {});
Stocké dansAprès un changement de scèneAprès un passage dans un autre espaceAprès un rechargement
storeouinonnon
variables globalesouiouinon
variables globales propres à l'espaceouinon — chaque espace garde les siennes, c'est voulunon
Fermer l'onglet efface tout

Rien de tout cela n'est conservé d'une visite à l'autre. Si vous avez besoin d'une valeur la prochaine fois, envoyez-la vous-même quelque part pendant que le visiteur est encore connecté.

Plugins​

Lisez et modifiez le composant personnalisé d'un plugin, et utilisez les déclencheurs et les étapes qu'un plugin ajoute :

const health = ctx.plugin('acme.health/health');   // '<plugin id>/<component id>'
for (const e of health.entities()) {
const max = health.props(e)!.max as number;
health.setState(e, { hp: max });
}

ctx.on('acme.patrol:reached', ({ point }) => {}); // le déclencheur d'un plugin
ctx.step('acme.patrol:pause', { seconds: 2 }); // l'étape d'un plugin

ctx.plugin a entities(), has(e), props(e), state(e), setState, setProps, setEnabled, add et remove. Ses types viennent du plugin : votre éditeur de code connaît donc les champs. Voir Code d'exécution.

Interface​

Lisez et modifiez les valeurs d'une carte d'interface, et réagissez à ses boutons :

const ui = ctx.getDivKit(entity);

ui.get('score');
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {});
ui.onAction('restart', () => {});

Voir Cartes d'interface.


Suivant : Recettes — des scripts complets à copier.