本文へスキップ

debuff で Earshot を使う ​

@debuffjp/plugin-earshot は Earshot をゲームにつなぎます。壁はタイルマップから読み取り、音は sfx バスに従い、シーンが変わると片付きます。

インストール ​

bash
npm install @debuffjp/plugin-earshot

audio.buffer() のある @debuffjp/audio が必要です。これは 0.29.2 の次のリリースで入ります。それより古いバージョンでは手動で接続してください。

セットアップ ​

ts
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)ある地点のリバーブ。座標はタイル単位なし
tile1 タイルあたりのワールド単位マップのタイル幅、または 1
bus"sfx" か "music"。音はそのバスの音量とミュートに従います"sfx"
params、materials、reverbs初期値。createListener と同じですEarshot のデフォルト

マップはライブに読み取るので、ドアを開けると音が通ります。一方通行の足場は壁になりません。タイルは tileWidth を使った正方形として扱います。マップの外は何もない空間として扱います。

ts
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 は座標か、動くものなら関数です。

ts
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 をマップに通します。

ts
const source = ear.source(node, { at: () => jukebox, name: "jukebox" });

name はインスペクトに表示する名前で、デフォルトは "source" です。シーンが変わると切り離されますが、ノードの開始と停止は自分で行います。

素材と壁 ​

materials は名前で壁の種類を追加または置き換えます。material(x, y) は各タイルの種類を返します。map がなければ、代わりに wallsBetween を渡します。

ts
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 ではなくパラメータで設定してください。

ts
game.params.group("earshot")?.set("silentRadius", 20);

それ以外はリスナー上でライブに変更できます。

ts
ear.listener.params.smoothing = 0.2;
ear.listener.materials.thin = { hz: 3000, db: -4 };

インスペクト ​

__DEBUFF_DEV__.state().plugins.earshot は各音の状態、距離、ゲイン、カットオフ、壁、リバーブを一覧にします。シナリオでプレイヤーに何が聞こえるかを確認できます。

json
{
  "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 はゲームの音量とミュートに従います。更新はフックから行います。

ts
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() を呼んでください。