Earshot 0.2:壁を聞き分けるボイスチャット
Earshot は、2D Web ゲームのサウンドをマップに合わせます。壁の向こうの仲間の声はこもり、洞窟では響き、叫び声は届く距離にいるモンスターだけを起こします。ランタイム依存はなく、Web Audio のコンテキストを渡せるエンジンなら、どれでも使えます。0.2 ではボイスチャットが加わりました。招待コードで入るルームは、自分の Cloudflare アカウントにデプロイする小さな Worker で動きます。
npm i @debuffjp/earshot| 音の位置 | プレイヤーに聞こえる音 | デフォルトのフィルターとゲイン |
|---|---|---|
| 遮るものなし、2 タイル未満 | 最大音量 | なし |
| 薄い壁の向こう | こもるが、言葉は聞き取れる | 2000 Hz、-6 dB |
| 厚い壁の向こう | さらにこもり、小さくなる | 1200 Hz、-12 dB |
| 2 枚以上の壁の向こう | ほとんど音色だけ | 700 Hz、-18 dB |
| 12 タイル以上 | 無音 | 音はオフ |
これらのデフォルト値は耳で調整しました。信号にどう作用するかの計測もしています。
マップを渡す
リスナーの位置と 2 点の間にあるものを Earshot に伝え、ゲームループから ear.update() を呼びます。あとはオーディオグラフの中で処理されます。
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 行で書けます。
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() がプレイヤーどうしを接続します。
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 枚置いてみてください。