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
| Nom | Ce que c'est |
|---|---|
ctx.entity | l'objet auquel ce script est attaché |
ctx.scene | la scène où se trouve l'objet |
ctx.space | l'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 formeon-keydowneton-keyup; - Press on the object (
on-mouse) arrive sous la formeon-mousedown,on-mouseupeton-click; - Hover (
on-hover) arrive sous la formeon-hovereton-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éclencheur | Ce 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-localized | l'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.
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.
Navigation
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
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.
awaitLa 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] });
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.
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é dans | Après un changement de scène | Après un passage dans un autre espace | Après un rechargement |
|---|---|---|---|
store | oui | non | non |
| variables globales | oui | oui | non |
| variables globales propres à l'espace | oui | non — chaque espace garde les siennes, c'est voulu | non |
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.