Aller au contenu principal

L'API ctx

ctx est votre poignée sur la scène en cours — il est passé à init et tout ce qui suit y est accroché.

Il est délibérément petit. Si vous cherchez quelque chose sans le trouver ici, il y a de bonnes chances que la réponse soit une étape intégrée plutôt qu'une API.

Où vous êtes

ctx.entityl'objet auquel ce script est attaché
ctx.scenela scène à laquelle il appartient
ctx.spacele monde

Cycle de vie et événements

ctx.tick((dt, t) => {});          // à chaque image ; dt et t en secondes
ctx.effect(() => {}); // relancé quand ce qu'il lit change ; peut renvoyer un nettoyage
ctx.on(trigger, (payload) => {}); // s'abonner à un déclencheur
ctx.emit(trigger, payload); // en lever un depuis cet objet

Les déclencheurs portent les mêmes noms que pour les événements et les patches. Ceux qui transportent une information utile :

DéclencheurVous obtenez
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 } — repos, déplacement, course ou saut
on-drag · on-pinch · on-rotate{ dx, dy } · { scale } · { angle }
on-vps-localizedoù le visiteur s'est avéré être

on-launch arrive au moment où votre instance est créée : impossible de le manquer en démarrant trop tard.

Trouver des objets

ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // le premier objet avec ces composants
ctx.query(LightComponent, TagsComponent); // tous
ctx.all();

Créer et supprimer

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

spawn est le pratique : donnez-lui une ressource et il assemble les composants qui lui correspondent. Un modèle devient un objet modèle, une image devient un plan texturé, un son devient une source audio.

Exécuter un comportement intégré

ctx.step('play_animation', { presetId }, { targets: [enemy] });

ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// les changements faits ici s'interpolent au lieu de sauter
});
C'est le raccourci que la plupart des gens ratent

ctx.step exécute n'importe laquelle des étapes intégrées — les mêmes que vos événements. Animation, changement de state, transitions de scène et transitions sont à un appel de distance : vous n'avez presque jamais besoin de les réimplémenter.

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

Entrées

Deux couches, pour deux travaux différents.

Les actions nommées lisent les liaisons de touches du projet : un visiteur qui remappe ses touches est respecté.

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

Les touches brutes lisent le clavier directement. Une pression dure exactement une image : lisez-les donc dans ctx.tick.

ctx.keyboard.down('KeyW');
ctx.keyboard.press('Space');
ctx.keyboard.press('ArrowLeft', { every: 200 }); // répétition automatique, en ms
ctx.keyboard.axis('KeyA', 'KeyD'); // -1, 0 ou 1
Les deux traitent les modificateurs différemment, exprès

Une liaison nommée ignore les modificateurs qu'elle ne mentionne pas — sprinter en tenant Maj ne doit pas annuler « avancer ». Un déclencheur de touche fait l'inverse : un modificateur que vous n'avez pas coché veut dire « ne doit pas être tenu ».

Perdre le focus de la fenêtre libère les touches tenues : rien ne reste coincé.

Caméra

ctx.camera.entity();           // l'objet caméra actif
ctx.camera.setActive(target); // changer de caméra ; null restaure celle par défaut
ctx.camera.pose(); // { position, rotation, forward } en coordonnées monde

Le transform de la caméra est la source de vérité dans tous les modes de contrôle : écrivez dedans pour la déplacer, lisez-le pour voir où les contrôles l'ont mise. En modes orbit et première personne, ce sont les contrôles qui possèdent l'orientation : une rotation que vous écrivez est écrasée — la position, elle, est respectée.

Raycast

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 }); // le rayon de votre choix
await ctx.raycast({ from: entity }); // depuis un objet, le long de son avant

Chaque impact vous donne l'objet, la distance, le point et la normale de surface en coordonnées monde — triés du plus proche au plus loin, un impact par objet.

Il renvoie une promesse, et ce n'est pas une erreur

Le rayon est lancé contre la géométrie réelle côté rendu : la réponse arrive donc à l'image suivante. Les objets invisibles et ceux marqués à ignorer (réticules, gizmos) sont sautés, si bien que ce qui est derrière répond au lieu que le rayon signale un raté.

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

La physique possède sa position. Utilisez teleport pour le placer et des impulsions ou des forces pour le déplacer.

Et passez skip quand vous tirez un rayon depuis l'intérieur de votre propre corps, sinon vous vous toucherez vous-même à chaque fois.

Les lectures viennent du dernier état synchronisé et accusent environ une image de retard — parfait pour « est-ce que je bouge ? », faux pour des calculs instantanés exacts.

Audio

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

Chaque appel démarre un son indépendant, ce qui est exactement ce qu'il faut pour des pas, des impacts et des tirs. Le composant audio n'a qu'une voix et se coupera lui-même — ne l'utilisez pas pour des effets.

Retenir des choses

Trois stockages, qui diffèrent par leur portée :

// 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. les globals — partagés avec tous les scripts, patches et événements
ctx.setGlobal('level', 3);
ctx.getGlobal('level');
ctx.subscribeGlobal('level', (v) => {});

// 3. les messages entre scripts
ctx.postMessage('enemy-died', { id });
ctx.handleMessage('enemy-died', ({ id }) => {});
Survit à un changement de scèneSurvit au passage entre spacesSurvit à un rechargement
storeouinonnon
globalsouiouinon
globals liés au spaceouinon — isolés exprèsnon
Rien ici ne survit à la fermeture de l'onglet

Aucun de ces trois n'est sauvegardé d'une visite à l'autre. Si quelque chose doit persister, envoyez-le vous-même quelque part tant que vous avez une connexion.

Interface

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.


Suite : Ce que vous construirez vraiment — des scripts complets à copier.