debuff で Earshot を使う
@debuffjp/plugin-earshot は Earshot をゲームにつなぎます。壁はタイルマップから読み取り、音は sfx バスに従い、シーンが変わると片付きます。
インストール
npm install @debuffjp/plugin-earshotaudio.buffer() のある @debuffjp/audio が必要です。これは 0.29.2 の次のリリースで入ります。それより古いバージョンでは手動で接続してください。
セットアップ
import { earshot } from "@debuffjp/plugin-earshot";
const ear = earshot({ audio, map, pos: () => player });
game.use(ear);pos は、Earshot の他の部分と同じくワールド座標です。
| オプション | 意味 | デフォルト |
|---|---|---|
audio | ゲームのオーディオ。必須 | |
pos() | リスナーの位置。必須 | |
map | 当たり判定のグリッドが壁になるタイルマップ | なし |
material(x, y) | 独自の名前を使うときの、タイルの素材 | 固いタイルは "solid" |
wallsBetween(a, b) | 独自の壁の判定。map がないときだけ使います。座標はタイル単位 | なし |
reverbAt(p) | ある地点のリバーブ。座標はタイル単位 | なし |
tile | 1 タイルあたりのワールド単位 | マップのタイル幅、または 1 |
bus | "sfx" か "music"。音はそのバスの音量とミュートに従います | "sfx" |
params、materials、reverbs | 初期値。createListener と同じです | Earshot のデフォルト |
マップはライブに読み取るので、ドアを開けると音が通ります。一方通行の足場は壁になりません。タイルは tileWidth を使った正方形として扱います。マップの外は何もない空間として扱います。
const ear = earshot({
audio,
map,
pos: () => player,
material: (x, y) => (map.collision[y * map.width + x] === 1 ? (isGlass(x, y) ? "thin" : "solid") : null),
reverbAt: (p) => (p.y > 20 ? "cave" : null),
});material はデフォルトの判定を置き換えるので、上の例のように固いタイルかどうかを自分で確かめてください。
play
音をデコードしてから、位置を指定して鳴らします。at は座標か、動くものなら関数です。
await audio.decode("door", assets.audio("door"));
ear.play("door", { at: door });
const hum = ear.play("radio", { at: () => radio, loop: true, volume: 0.8, rate: 1 });
hum.source.state; // "audible" | "out-of-range" | "muted"
hum.stop();play は Earshot の音源と stop() を返します。デコードしていない音はエラーを投げ、その旨を伝えます。音は鳴り始めるときにミックスを受け取るので、一瞬でもフィルターなしで鳴ることはありません。
source
自分の AudioNode をマップに通します。
const source = ear.source(node, { at: () => jukebox, name: "jukebox" });name はインスペクトに表示する名前で、デフォルトは "source" です。シーンが変わると切り離されますが、ノードの開始と停止は自分で行います。
素材と壁
materials は名前で壁の種類を追加または置き換えます。material(x, y) は各タイルの種類を返します。map がなければ、代わりに wallsBetween を渡します。
const ear = earshot({
audio,
pos: () => player,
materials: { glass: { hz: 5000, db: -3 } },
wallsBetween: (a, b) => wallsOnGrid(tiles, a, b),
});デフォルト値と、壁が 2 枚以上のときのルールはサウンドにあります。
パラメータ
fullRadius、silentRadius、panWidth は earshot というパラメータグループに現れるので、あなたやエージェントがゲームの実行中に調整できます。プラグインはゲームの更新 3 回ごとにこれらをリスナーへコピーしてから音を更新します。この 3 つは ear.listener.params ではなくパラメータで設定してください。
game.params.group("earshot")?.set("silentRadius", 20);それ以外はリスナー上でライブに変更できます。
ear.listener.params.smoothing = 0.2;
ear.listener.materials.thin = { hz: 3000, db: -4 };インスペクト
__DEBUFF_DEV__.state().plugins.earshot は各音の状態、距離、ゲイン、カットオフ、壁、リバーブを一覧にします。シナリオでプレイヤーに何が聞こえるかを確認できます。
{
"sounds": [
{ "name": "door", "state": "audible", "distance": 7, "gain": 0.13, "cutoff": 1200, "walls": ["solid"], "reverb": null }
]
}シーンの片付け
音は、終わったとき、止めたとき、シーンが変わったときに取り除かれます。新しいシーンの enter() で始めた音はそのシーンのものなので残ります。game.dispose() はリスナーも含めてすべて取り除きます。
ボイスとリスナー
ear.listener は Earshot のリスナーで、ボイスなどプラグインが包んでいないものに使います。ボイスルームには listener: ear.listener として渡します。この方法で追加したボイスはインスペクトに現れず、シーンが変わっても終わらないので、自分で取り除いてください。更新はプラグインが他の音と一緒に行います。
手動で接続する
debuff で Earshot を直接使うには、debuff のオーディオコンテキストとバスを渡します。Earshot はゲームの音量とミュートに従います。更新はフックから行います。
import { createListener, wallsOnGrid } from "@debuffjp/earshot";
const ear = createListener(audio.ctx, {
output: audio.bus("sfx"),
tile: 16,
pos: () => player,
world: { wallsBetween: (a, b) => wallsOnGrid(tiles, a, b) },
});
const off = game.hooks.afterUpdate(() => ear.update());
const buffer = await audio.ctx.decodeAudioData(assets.audio("door"));
const node = new AudioBufferSourceNode(audio.ctx, { buffer });
const door = ear.source(node, { pos: () => doorPosition });
node.onended = () => door.remove();
ear.update();
node.start();この場合、シーンの片付けは自分で行います。シーンを離れるときに door.remove() を、使い終わったら off() と ear.dispose() を呼んでください。