feat(hunt): 《围猎》2-4人实时联机纯音频协作守卫——纯音频MMORPG第一阶段切片
- 设计:docs/design/23-hunt.md + voice-ids(84 id,166条)+ 13号断言登记,§15记录10+1条设计裁决 - 服务器:零依赖WebSocket(ws.js)+ 房间管理/服务端权威战斗/bot AI(hunt-room.js),断线30s宽限resume回槽、超时转bot;修复package.json模块类型误判的既有破坏 - 客户端:web/hunt/ 全套(教学T0-T4/听觉视野裁剪R1-R8/结算排行榜/guide接入),playtest 211断言 - 引擎:audio.js 后向兼容 opts.pan(StereoPannerNode),web-engine-api.md 同步 - 资产:166条TTS语音(QC无离群)+ 13件合成sfx(真峰值≤-3dBFS)+ 2首Mureka BGM,终检全绿 - 平衡:bot调参至挂机局71%失守/C、3bot局71% A/B(hunt-balance-scan.mjs 24种子扫描) - 接线:大厅卡片、profiles.js hunt桶、README/roadmap MMO路线条目 验证:hunt-sim 16/16 + api-smoke 12/12 + hunt playtest 211/211 + 既有10游戏与引擎回归零回退 + 真服务器WS端到端冒烟通过
This commit is contained in:
+38
-5
@@ -226,6 +226,10 @@ export class WebAudioEngine {
|
||||
* @param {string} id 'bgm/xxx' | 'voice/xxx' | 'sfx/xxx'(可带扩展名)
|
||||
* @param {object} [opts]
|
||||
* @param {number} [opts.volume] 本次播放音量 0~1(乘层音量)
|
||||
* @param {number} [opts.pan] 可选声像 -1~1(sfx/voice 两层):非 0 有限数时
|
||||
* 在 gain 与层总线之间插 StereoPannerNode;缺省/0
|
||||
* 走原路径(零行为变化);老 Safari 无
|
||||
* createStereoPanner 时静默忽略(23-hunt §9)
|
||||
* @param {number} [opts.fadeMs] 仅 BGM:本次交叉淡入时长
|
||||
* @param {boolean} [opts.loop=true] 仅 BGM:是否循环
|
||||
* @param {() => void} [opts.onComplete] 仅 voice:自然播完回调(被打断不触发)
|
||||
@@ -474,9 +478,9 @@ export class WebAudioEngine {
|
||||
* → gain → voiceBus。自然结束用定时器判定(源消耗位置 ÷ 当前 tempo),
|
||||
* 支持中途改 tempo(长按快进)并随之修正结束时刻。
|
||||
*/
|
||||
async _playVoice(id, { volume = 1, onComplete, queue = false } = {}) {
|
||||
async _playVoice(id, { volume = 1, onComplete, queue = false, pan } = {}) {
|
||||
if (queue && this._voice) {
|
||||
this._voiceQueue.push({ id, volume, onComplete });
|
||||
this._voiceQueue.push({ id, volume, onComplete, pan });
|
||||
return;
|
||||
}
|
||||
// 默认顶掉当前对白(不打 stopVoice() 以免清掉 queue 语义)
|
||||
@@ -526,7 +530,15 @@ export class WebAudioEngine {
|
||||
const gain = this.ctx.createGain();
|
||||
gain.gain.value = this.voiceVolume * volume;
|
||||
node.connect(gain);
|
||||
gain.connect(this._voiceBus);
|
||||
// 可选声像(23-hunt §9,对称能力一次给足;本作旁白不使用):pan ∈ [-1,1]
|
||||
// 时 panner 插在 voice gain 与 voiceBus 之间;缺省/0 → 原路径不变
|
||||
const panner = this._makePanner(pan);
|
||||
if (panner) {
|
||||
gain.connect(panner);
|
||||
panner.connect(this._voiceBus);
|
||||
} else {
|
||||
gain.connect(this._voiceBus);
|
||||
}
|
||||
|
||||
const token = {
|
||||
node, gain, st, buf, filter, srcBuf, // srcBuf = 尾部垫静音后的播放源
|
||||
@@ -631,7 +643,7 @@ export class WebAudioEngine {
|
||||
this._armVoiceEnd(cur);
|
||||
}
|
||||
|
||||
async _playSfx(id, { volume = 1 } = {}) {
|
||||
async _playSfx(id, { volume = 1, pan } = {}) {
|
||||
const buf = await this._load(id);
|
||||
if (!buf) return;
|
||||
const source = this.ctx.createBufferSource();
|
||||
@@ -639,7 +651,28 @@ export class WebAudioEngine {
|
||||
const gain = this.ctx.createGain();
|
||||
gain.gain.value = this.sfxVolume * volume;
|
||||
source.connect(gain);
|
||||
gain.connect(this._sfxBus);
|
||||
// 可选声像(23-hunt §9):pan ∈ [-1,1] 时在 gain 与总线之间插
|
||||
// StereoPannerNode;缺省/0 → 原路径逐字节不变(不建 panner 节点)
|
||||
const panner = this._makePanner(pan);
|
||||
if (panner) {
|
||||
gain.connect(panner);
|
||||
panner.connect(this._sfxBus);
|
||||
} else {
|
||||
gain.connect(this._sfxBus);
|
||||
}
|
||||
source.start(); // 即发即弃,天然支持并发叠加
|
||||
}
|
||||
|
||||
/**
|
||||
* 可选声像节点(opts.pan,-1..1)。pan 缺省/非有限数/0 → null(调用方走
|
||||
* 原路径,零行为变化);老 Safari 无 ctx.createStereoPanner → null
|
||||
* (静默降级:不炸、不报错音,声音照常从原路径出)。
|
||||
*/
|
||||
_makePanner(pan) {
|
||||
if (typeof pan !== 'number' || !isFinite(pan) || pan === 0) return null;
|
||||
if (!this.ctx || typeof this.ctx.createStereoPanner !== 'function') return null;
|
||||
const panner = this.ctx.createStereoPanner();
|
||||
panner.pan.value = Math.max(-1, Math.min(1, pan));
|
||||
return panner;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user