项目整理:重写主文档、归档过时文档、清理死代码、新增音频制作手册
文档整理: - 重写 game/操作说明.md 为项目主文档(6游戏/6手势/旁白/模型生成音频/TTS两步流程) - 归档过时文档至 other_docs/(林夏原始设计、旧执行文档) - 新增 docs/音频制作执行手册.md(从零制作游戏音频的完整流程) - docs/ 下保留旁白音色提示词、音频脚本 代码清理: - 删除旧架构死代码: story.js / gestures.js / profile.js - 删除耦合旧架构的过时测试目录 game/tests/ 脚本配置修正: - check_tts_env.sh: 启动脚本路径 AutoVideo → tts-server(实际位置) - gen_narrator_samples.py: 硬编码地址改为环境变量,默认对齐 tts-server 端口 8000
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// 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 };
|
||||
})();
|
||||
Reference in New Issue
Block a user