Multijoueur
Tout ce qui suit n'est actif que si votre projet a le multijoueur activé et que le lecteur a ouvert une salle.
Le reste du temps, ces appels ne font discrètement rien, et c'est délibéré : un script écrit pour le multijoueur tourne parfaitement bien dans un aperçu solo. Vous n'avez pas besoin de deux versions.
ctx.net.id; // l'identifiant de ce participant
ctx.net.peers(); // tout le monde actuellement dans la salle
ctx.net.onJoin((peer) => {});
ctx.net.onLeave((peer) => {});
Ce qui se réplique tout seul
Seulement les positions des corps physiques marqués partagés. Tout le reste, vous l'envoyez vous-même.
Cela semble peu, et c'est exactement le bon peu : une caisse, une porte ou une balle doivent être un objet sur lequel tout le monde s'accorde, tandis qu'un personnage joueur ne le doit surtout pas — sinon une seule personne qui marche déplacerait l'avatar de tous. → propriété
Messages
Pour les événements : quelqu'un a marqué, une manche a commencé, une porte a été ouverte.
ctx.net.send('score', { points: 10 });
ctx.net.on('score', ({ from, data }) => {
/* … */
});
On envoie et on oublie, à tout le monde sauf soi.
État partagé
Pour les valeurs sur lesquelles tout le monde doit s'accorder. C'est une simple table où la dernière écriture l'emporte, répliquée à tous.
ctx.net.state.set(ctx.net.id, { name, colour, ready: true });
ctx.net.state.get(somePeerId);
ctx.net.state.all();
ctx.net.state.on(ctx.net.id, (value) => {});
Indexer par ctx.net.id est le schéma standard pour l'état par joueur : chacun écrit son entrée et
lit celles des autres.
Faire apparaître pour tout le monde
const id = ctx.net.spawn(props.bullet, { position, rotation });
ctx.net.despawn(id);
L'identifiant revient immédiatement, mais l'objet apparaît une fois que la salle l'a confirmé. Cet aller-retour est ce qui garde l'identifiant identique sur chaque client, ce qui permet ensuite à la physique de s'accorder dessus.
Seul celui qui a fait apparaître quelque chose peut le faire disparaître, et quitter la salle emporte vos apparitions avec vous.
Dessiner les autres en douceur
C'est la partie qui rate le plus souvent : elle a donc son appel dédié.
ctx.net.follow(otherPlayerObject, { position, rotation });
ctx.net.unfollow(otherPlayerObject);
Appelez-le avec chaque position que vous recevez — dix à vingt fois par seconde est normal — et le runtime écrit le transform à chaque image à votre place, en rejouant le tampon légèrement en retard pour toujours disposer de deux positions connues entre lesquelles se déplacer.
Les paquets arrivent bien moins souvent que les images : l'objet reste immobile puis saute, et les joueurs distants semblent avancer par à-coups plutôt que marcher. Adoucir vers le paquet le plus récent n'y change rien non plus — entre deux paquets il n'y a rien devant l'instant présent vers quoi tendre, donc cela cale puis tressaute.
Réinjecter la même position ne coûte rien : appeler follow à chaque image avec ce que vous avez
sous la main est l'usage prévu.
Une organisation qui marche
Si vous partez de zéro, cette répartition des rôles vous mènera loin :
- chaque joueur possède son propre personnage — un corps local, piloté par ses entrées ;
- sa position part via
net.stateou un canal, dix à vingt fois par seconde ; - les personnages des autres sont dessinés avec
follow; - les objets du monde sont des corps partagés, la physique les garde donc cohérents gratuitement ;
- le score et l'état de la manche vivent dans
net.state, écrits par celui qui fait autorité pour cet événement.
Suite : Dépannage — quand quelque chose ne se comporte pas bien.