Zum Hauptinhalt springen

Mehrspieler

Alles Folgende ist nur dann lebendig, wenn Ihr Projekt Mehrspieler eingeschaltet hat und der Player einen Raum geöffnet hat.

Sonst tun diese Aufrufe still gar nichts, und das ist Absicht: Ein für Mehrspieler geschriebenes Skript läuft in einer Einzelspieler-Vorschau völlig problemlos. Sie brauchen keine zwei Versionen.

ctx.net.id;        // die Id dieses Teilnehmers
ctx.net.peers(); // alle, die gerade im Raum sind
ctx.net.onJoin((peer) => {});
ctx.net.onLeave((peer) => {});

Was sich von selbst repliziert

Nur die Positionen von Physikkörpern, die als shared markiert sind. Alles andere senden Sie selbst.

Das klingt nach wenig, und es ist genau das richtige Wenig: Eine Kiste, eine Tür oder ein Ball sollen ein Objekt sein, auf das sich alle einigen, eine Spielfigur darf das keinesfalls — sonst bewegt eine gehende Person die Avatare aller. → Zugehörigkeit

Nachrichten

Für Ereignisse: jemand hat gepunktet, eine Runde hat begonnen, eine Tür wurde geöffnet.

ctx.net.send('score', { points: 10 });
ctx.net.on('score', ({ from, data }) => {
/* … */
});

Abschicken und vergessen, an alle außer Sie selbst.

Geteilter Zustand

Für Werte, auf die sich alle einigen müssen. Es ist eine einfache Zuordnung, bei der die letzte Schreiboperation gewinnt, an alle repliziert.

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) => {});

Nach ctx.net.id zu schlüsseln ist das Standardmuster für Zustand pro Spieler: Jeder schreibt seinen eigenen Eintrag und liest die der anderen.

Für alle spawnen

const id = ctx.net.spawn(props.bullet, { position, rotation });
ctx.net.despawn(id);

Die Id kommt sofort zurück, aber das Objekt erscheint, sobald der Raum es bestätigt. Dieser Rundlauf hält die Id auf jedem Client identisch — und genau das lässt die Physik sich danach über sie einigen.

Nur wer etwas gespawnt hat, darf es despawnen, und den Raum zu verlassen nimmt Ihre Spawns mit.

Andere Leute weich zeichnen

Das ist der Teil, der am häufigsten schiefgeht, deshalb gibt es dafür einen eigenen Aufruf.

ctx.net.follow(otherPlayerObject, { position, rotation });
ctx.net.unfollow(otherPlayerObject);

Rufen Sie ihn mit jeder empfangenen Position auf — zehn- bis zwanzigmal pro Sekunde ist normal — und die Runtime schreibt stattdessen jedes Bild den Transform, indem sie den Puffer leicht verzögert abspielt und so immer zwei bekannte Positionen hat, zwischen denen sie sich bewegen kann.

Schreiben Sie eingehende Positionen nicht direkt in einen Transform

Pakete treffen weit seltener ein als Bilder, also steht das Objekt still und springt dann: Entfernte Spieler wirken, als stapften sie, statt zu gehen. Weich zur neuesten Nachricht zu gleiten hilft ebenfalls nicht — zwischen zwei Paketen liegt vor dem aktuellen Moment nichts, worauf man zugleiten könnte, also stockt es und ruckt dann.

Dieselbe Position erneut einzuspeisen kostet nichts: follow in jedem Bild mit dem aufzurufen, was Sie gerade haben, ist die vorgesehene Nutzung.

Eine Aufteilung, die funktioniert

Wenn Sie bei null anfangen, bringt Sie diese Arbeitsteilung weit:

  • jeder Spieler besitzt seine eigene Figur — ein lokaler Körper, von seinen Eingaben getrieben;
  • seine Position geht hinaus über net.state oder einen Kanal, zehn- bis zwanzigmal pro Sekunde;
  • die Figuren aller anderen werden mit follow gezeichnet;
  • die Objekte der Welt sind geteilte Körper, die Physik hält sie also gratis konsistent;
  • Punktestand und Rundenzustand leben in net.state, geschrieben von dem, der für dieses Ereignis maßgeblich ist.

Weiter: Fehlersuche — wenn sich etwas nicht richtig verhält.