// narrator.js — 旁白系统 // 全程语音播报:场景描述、操作提示、状态反馈。 // 这是无障碍核心:玩家闭眼也能完全掌握游戏状态。 const Narrator = (() => { let _voice = null; let _busy = false; let _queue = []; // [{ text, resolve }] let _repTimer = null; let _repFn = null; // ── 初始化:找最佳中文语音 ── function init() { return new Promise(resolve => { if (!window.speechSynthesis) { resolve(); return; } const load = () => { const vs = speechSynthesis.getVoices(); _voice = vs.find(v => v.lang === 'zh-CN') || vs.find(v => v.lang.startsWith('zh')) || vs[0] || null; resolve(); }; if (speechSynthesis.getVoices().length > 0) load(); else speechSynthesis.addEventListener('voiceschanged', load, { once: true }); }); } // ── 底层单句播放 ── function _speak(text) { return new Promise(resolve => { if (!window.speechSynthesis) { resolve(); return; } const u = new SpeechSynthesisUtterance(text); u.lang = 'zh-CN'; u.rate = 0.88; u.pitch = 1.0; u.volume = 1.0; if (_voice) u.voice = _voice; u.onend = resolve; u.onerror = resolve; speechSynthesis.speak(u); }); } // ── 推进队列 ── async function _next() { if (_busy || _queue.length === 0) return; _busy = true; const item = _queue.shift(); dbg(`[N] ${item.text.slice(0, 40)}`); await _speak(item.text); _busy = false; if (item.resolve) item.resolve(); _next(); } // ── 排队说(fire-and-forget)── function say(text, onDone) { _queue.push({ text, resolve: onDone || null }); _next(); } // ── 说完再继续(await 用)── function sayAndWait(text) { return new Promise(resolve => { _queue.push({ text, resolve }); _next(); }); } // ── 立刻打断当前语音,说新内容 ── function interrupt(text, onDone) { if (window.speechSynthesis) speechSynthesis.cancel(); _queue = []; _busy = false; say(text, onDone); } // ── 宣告操作选项 + 每12秒自动重复 ── function options(tapLabel, dtLabel) { clearTimeout(_repTimer); let tip = ''; if (tapLabel && dtLabel) tip = `单击${tapLabel},双击${dtLabel}。`; else if (tapLabel) tip = `单击${tapLabel}。`; else if (dtLabel) tip = `双击${dtLabel}。`; _repFn = () => { interrupt(`——还在等你。${tip}`); _repTimer = setTimeout(_repFn, 14000); }; say(tip); _repTimer = setTimeout(_repFn, 14000); } // ── 清除重复定时器(选择后调用)── function clearOptions() { clearTimeout(_repTimer); _repFn = null; } // ── 停止一切 ── function stop() { if (window.speechSynthesis) speechSynthesis.cancel(); _queue = []; _busy = false; clearOptions(); } return { init, say, sayAndWait, interrupt, options, clearOptions, stop }; })();