本文へスキップ

Earshot 0.2:壁を聞き分けるボイスチャット ​

Earshot は、2D Web ゲームのサウンドをマップに合わせます。壁の向こうの仲間の声はこもり、洞窟では響き、叫び声は届く距離にいるモンスターだけを起こします。ランタイム依存はなく、Web Audio のコンテキストを渡せるエンジンなら、どれでも使えます。0.2 ではボイスチャットが加わりました。招待コードで入るルームは、自分の Cloudflare アカウントにデプロイする小さな Worker で動きます。

bash
npm i @debuffjp/earshot
音の位置プレイヤーに聞こえる音デフォルトのフィルターとゲイン
遮るものなし、2 タイル未満最大音量なし
薄い壁の向こうこもるが、言葉は聞き取れる2000 Hz、-6 dB
厚い壁の向こうさらにこもり、小さくなる1200 Hz、-12 dB
2 枚以上の壁の向こうほとんど音色だけ700 Hz、-18 dB
12 タイル以上無音音はオフ

これらのデフォルト値は耳で調整しました。信号にどう作用するかの計測もしています。

マップを渡す ​

リスナーの位置と 2 点の間にあるものを Earshot に伝え、ゲームループから ear.update() を呼びます。あとはオーディオグラフの中で処理されます。

ts
import { createListener, wallsOnGrid } from "@debuffjp/earshot";

const ear = createListener(ctx, {
  tile: 16,
  pos: () => player,
  world: {
    wallsBetween: (a, b) => wallsOnGrid(tiles, a, b),
    reverbAt: (p) => (p.y > 20 ? "cave" : null),
  },
});

const radio = ear.source(radioNode, { pos: () => radioPosition });

// In your game loop. 10 to 20 times a second is enough.
ear.update();

どの AudioNode も音源にできます。効果音も、部屋の隅のジュークボックスの音楽も、プレイヤーの声も、同じルールに従います。

声をコントローラーに ​

createLoudness はマイクの音量を、部屋の環境音を基準にした 0〜3 のレベルに変換します。音声はアナライザーまでしか届かず、録音、送信、再生はされません。レベルと壁を組み合わせれば、ステルスのルールが 3 行で書けます。

ts
const walls = wallsOnGrid(tiles, player, monster);
const mix = computeMix(player, monster, walls, DEFAULT_PARAMS, DEFAULT_MATERIALS);
if (loud.level >= 2 && heardDb(loud.level, mix) >= 6) monster.wake();

どのレベルも press() でキーから入力できるので、マイクのないプレイヤーも同じゲームを遊べます。

ゲーム世界の中のボイスチャット ​

0.2 の新機能 ear.voice() は、リモートプレイヤーのストリームを同じ壁と部屋に通します。RTCPeerConnection がすでにあるなら、これだけで足ります。ない場合は joinVoiceRoom() がプレイヤーどうしを接続します。

ts
const joined = await joinVoiceRoom({
  url: "wss://earshot-voice.you.workers.dev",
  code: createRoomCode(),
  mic,
  listener: ear,
  ipPolicy: "relay",
  pos: (peer) => players.get(peer) ?? player, // Until their first position arrives.
  onMessage: (peer, position) => players.set(peer, position),
});

2 つの判断は、意図してゲーム側に残しています。1 つ目はマイクです。canUseVoice 関数が許可するまでマイクは開きません。同意の確認や、ポータルの disableChat のようなフラグはここで扱います。2 つ目は ipPolicy で、デフォルト値はありません。"relay" は他のプレイヤーから IP アドレスを隠し、音声は TURN のトラフィックとして課金されます。"direct" は安く済みますが、互いの IP アドレスが見えます。プレイヤー間のライブ音声には法的な義務を伴う場合があり、子どもが遊ぶゲームでは特に注意が必要です。公開する前にボイスチャットのドキュメントを読んでください。

サーバーの費用 ​

私たちは何もホストしません。Worker テンプレートは自分のアカウントで動きます。プレイヤー間の接続メッセージを中継し、人数の上限を守らせ、TURN の認証情報を発行します。ログインや BAN の確認を書ける canJoin 関数もあります。これらの確認を加えるまでは、ルームコードを知っている人なら誰でも参加できます。

費用がかさむのはリレーです。そこで、最悪のケースを計算しました。全員が話し続け、32 kbps の Opus にパケットのオーバーヘッドを加えると、1 ストリームあたり 1 時間で約 24 MB です。Cloudflare は TURN の送信トラフィックに対し、毎月 1,000 GB の無料枠を超えた分を 1 GB あたり $0.05 で課金します(料金)。全員が全員に接続するので、n 人のルームには n x (n - 1) 本のストリームが流れます。4 人なら 12 本、8 人なら 56 本です。

ルーム(全員リレー)1 時間あたりの GB無料枠を超えた後の 1 時間あたりの費用無料枠に収まるルーム時間
4 人0.29$0.014約 3,470
8 人1.34$0.067約 740

実際のルームの費用はこれより少なくなります。十分に離れたプレイヤーへの送信は止まるからです。プッシュトゥトークではキーを離している間は無音を送るので、データは小さくなりますが、トラフィックはなくなりません。この表は TURN だけの計算です。ルームサーバーの Workers と Durable Objects の利用料は別に課金されます。

まず Earshot のドキュメントを読み、プレイヤーとラジオの間に壁を 1 枚置いてみてください。