マルチプレイヤー
以下はすべて、プロジェクトでマルチプレイヤーが有効で、かつプレイヤーがルームを開いている ときだけ効きます。
それ以外のときは、これらの呼び出しは黙って何もしません。これは意図的です。マルチプレイヤー用に 書いた script も、1 人用のプレビューでそのまま問題なく動きます。2 つのバージョンは要りません。
ctx.net.id; // この参加者の id
ctx.net.peers(); // いまルームにいる全員
ctx.net.onJoin((peer) => {});
ctx.net.onLeave((peer) => {});
自動で複製されるもの
shared と印を付けた物理ボディの位置だけです。それ以外は自分で送ります。
少なく聞こえますが、この少なさが正しいのです。木箱・ドア・ボールは、全員が同じものとして合意する 1 つのオブジェクトであるべきですが、プレイヤーキャラクターはそうであってはいけません。さもないと、 1 人が歩いただけで全員のアバターが動いてしまいます。 → 所有
メッセージ
出来事を伝えるためのものです。誰かが得点した、ラウンドが始まった、ドアが開いた、など。
ctx.net.send('score', { points: 10 });
ctx.net.on('score', ({ from, data }) => {
/* … */
});
送りっぱなしで、自分以外の全員に届きます。
共有状態
全員が合意する必要のある値のためのものです。最後の書き込みが勝つ単純なマップで、全員に複製されます。
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) => {});
ctx.net.id をキーにするのが、プレイヤーごとの状態を扱う定石です。各自が自分のエントリーを書き、
他人のものを読みます。
全員にスポーンさせる
const id = ctx.net.spawn(props.bullet, { position, rotation });
ctx.net.despawn(id);
id はすぐ返りますが、オブジェクトが現れるのはルームが確認したあとです。この往復があるおかげで id がすべてのクライアントで同一になり、そのあと物理がそれについて合意できます。
スポーンさせたものを消せるのは、スポーンさせた本人だけです。ルームを出ると、自分のスポーンも 一緒に消えます。
他人をなめらかに描く
いちばんよく失敗するのがここなので、専用の呼び出しが用意されています。
ctx.net.follow(otherPlayerObject, { position, rotation });
ctx.net.unfollow(otherPlayerObject);
受け取った位置ごとにこれを呼んでください。毎秒 10〜20 回が普通です。あとはランタイムが毎フレーム transform を書き、バッファを少し遅らせて再生します。こうすれば、動かす先として既知の位置が常に 2 つある状態を保てます。
パケットはフレームよりずっと少ない頻度で届くので、オブジェクトは止まっては跳ぶ、という動きに なります。遠くのプレイヤーは歩くのではなく、飛び飛びに移動して見えます。最新のパケットへ なめらかに近づける方法も解決になりません。パケットとパケットの間には、現在より先に向かう先が ないので、止まってから急に動きます。
同じ位置を繰り返し渡してもコストはゼロなので、いま手元にある値で毎フレーム follow を呼ぶのが
想定された使いかたです。
うまくいく役割分担
ゼロから始めるなら、この分担でかなり先まで行けます。
- プレイヤーは自分のキャラクターを自分で持つ。 ローカルのボディを、自分の入力で動かします
- その位置を外へ送る。
net.stateかチャンネルで、毎秒 10〜20 回 - 他人のキャラクターは
followで描く - 世界のオブジェクトは shared なボディにする。 物理がただで整合性を保ってくれます
- スコアとラウンドの状態は
net.stateに置く。 その出来事について権限を持つ側が書き込みます
次へ: トラブルシューティング — うまく動かないとき。