- 01-identity:推荐自动匿名档案(首访 0 交互)+ Netflix 式切换器 + 配对码跨设备;passkey 因 LAN HTTP 不可用否决;审计出 7/8 游戏跨设备只 PUT 不 GET、大厅零 aria、玩家名不可朗读三个存量问题 - 02-input-parity:方向键/WASD+空格+回车(双击即时)键位(audiogames 社区惯例同构);语音双套资产路线(153 句含操作词,306 TTS/37min);MVP ~200 行 1-2 人日 - 03-data-sync:设备 token 即档案、localStorage 按 profileId 分桶(修同设备串档)、桶级 LWW + baseUpdatedAt 守卫、claim 可选认领 + PIN scrypt;公网前 8 条清单 - 00-synthesis:需求映射 + 架构收敛 + P0/P1 分期 + 3 个待拍板决策点
320 lines
29 KiB
Markdown
320 lines
29 KiB
Markdown
# 02 · PC 键盘操作与双端一致体验(Input Parity)调研
|
||
|
||
> 2026-09-09 · 交互调研 · 范围:PC 浏览器键盘操作方案、引擎适配、提示(画面+语音)随平台适配、混合设备、测试管线影响。
|
||
> 红线遵守:本文只做调研与设计,不改任何代码。所有数字均从仓库实测/统计得出,来源随文标注。
|
||
|
||
## 0. 结论速览
|
||
|
||
| 决策点 | 推荐 | 一句话理由 |
|
||
|---|---|---|
|
||
| 键位方案 | 方向键=四向滑动、**空格=单击、回车=双击**、按住空格=长按(WASD 作方向别名) | 与 A Blind Legend / audiogames.net 社区惯例完全同构,双击走专用键避免把 300ms 延迟带进键盘 |
|
||
| 语音路线 | **路线 a:双套资产**(`voice/<gender>/key/<id>` 变体目录,复用 male/female 机制) | 与路线 b 重录成本相同(306 文件/37 分钟音频),但增量落地不动已验收资产、两端语气都精确 |
|
||
| 最小可用(MVP) | 引擎键盘通道 + 遮罩键盘解锁 + 画面提示自适应,**语音暂不动** | ~200 行改动(gesture.js/audio.js/settings.js/index.html),1~2 人日;语音资产独立分期 |
|
||
|
||
---
|
||
|
||
## 1. 现状审计
|
||
|
||
### 1.1 gesture.js(web/engine/gesture.js,323 行):三通道中的两个,无键盘
|
||
|
||
- 输入通道:**touch 事件**(主通道,全 preventDefault 防滚动/缩放/合成鼠标)+ **mouse 事件**(桌面调试通道,带 700ms `recentlyTouched()` 守卫防触摸后的合成鼠标误判)。**没有任何 keydown/keyup 监听**——全仓库 grep `keyboard|keydown` 零命中。
|
||
- 手势模型:`up/down/left/right/tap/doubletap/longpress/longpressend`(+immediateTap 模式的 `tapcancel`)。核心状态机是**指针生命周期**(down→move→up)+ 长按 450ms 定时器 + 双击 300ms 窗。
|
||
- 关键内部函数:`handleDown(x,y)` / `handleMove(x,y)` / `handleUp(x,y)` / `handleCancel()`——**坐标只用于位移判定**(tapSlop/swipeMinDistance),点按与长按判定本身与坐标值无关。这意味着键盘通道可以把"虚拟指针"喂给同一状态机:坐标传元素中心、永不 move,天然只会走出 tap/longpress/doubletap 分支,永远不会误判滑动。
|
||
- `attachGestures(element, onGesture, config)` 是唯一接线点,10 个游戏 index.html 全部经它接 `game.onGesture`。**键盘层加在 attachGestures 内部 = 游戏代码零改动**。
|
||
- immediateTap(rhythm 独占,`web/rhythm/index.html:33`):按下即发 tap、双击补发改判。键盘空格若复用 handleDown/handleUp,rhythm 的"空格=kick 演奏"自动成立,零改动。
|
||
- 长按旁白快进(1.7x):靠 window 事件 `soundgame:longpress/longpressend`(audio.js 监听)。键盘长按若复用同一状态机,这两个事件照发,快进零改动。
|
||
|
||
### 1.2 启动遮罩(web/engine/audio.js showStartOverlay):只认触摸和鼠标 click
|
||
|
||
```js
|
||
overlay.addEventListener('touchstart', e => e.preventDefault(), {passive:false});
|
||
overlay.addEventListener('touchend', e => { e.preventDefault(); onTap(); }, {passive:false});
|
||
overlay.addEventListener('click', onTap);
|
||
```
|
||
|
||
- 9 个游戏页 + template 全部传 `text: '轻触开始'`(audio.js 默认值同)——**画面提示的第一个适配点**,且键盘玩家目前**无法用键盘解锁音频**(keydown 不是合法解锁手势路径),必须伸手去摸鼠标。注:浏览器规范里 keydown 属于 user activation 事件,`AudioContext.resume()` 在 keydown 回调内同步发起即可解锁,技术可行,只是遮罩没监听。
|
||
- 遮罩期间 gesture.js 尚未 attach(index.html 先 `await showStartOverlay()` 再 `attachGestures`),所以键盘解锁必须改在 audio.js 里。
|
||
|
||
### 1.3 画面提示现状:游戏页几乎无视觉文案,集中在三处
|
||
|
||
1. 启动遮罩 hint(audio.js,`轻触开始`×10 页)
|
||
2. 大厅 footer(web/index.html:209-211):「操作方式:上 / 下 / 左 / 右滑动、单击、双击」
|
||
3. 各游戏 index.html 只有 cover.png 封面 + 遮罩,**无其他操作文案**——画面提示适配的面积非常小。
|
||
|
||
### 1.4 语音提示现状:操作词烘焙在 TTS 资产里(核心约束)
|
||
|
||
以 `tools/gen_voice_dual.py` 台词表(8 游戏全量 651 句,TTS 实际数据源)统计含「滑/单击/双击/长按」的句子(regex 实测,2026-09-09):
|
||
|
||
| 游戏 | 台词总数 | 含操作词句数 |
|
||
|---|---|---|
|
||
| storyline | 41 | 21 |
|
||
| escape | 80 | 26 |
|
||
| simon | 78 | 22 |
|
||
| rhythm | 40 | 9 |
|
||
| space | 59 | 24 |
|
||
| rpg | 123 | 16 |
|
||
| radio | 103 | 18 |
|
||
| dungeon | 127 | 17 |
|
||
| **合计** | **651** | **153(23.5%)** |
|
||
|
||
(注:任务书里「space 27 / dungeon 22 / radio 19」来自对 `docs/design/*-voice-ids.md` 的表格行 grep;voice-ids 文档只覆盖 5 个游戏且部分行多句,本文以 gen 脚本台词表为准——它是 TTS 管线的真实输入。)
|
||
|
||
受影响资产实测量(ffprobe 逐文件探测,全部存在):
|
||
|
||
- 153 句 × 2 性别 = **306 个音频文件**,双格式(.ogg+.m4a)共 612 个文件、**49.4 MiB**;
|
||
- 音频总时长:male 1078.8s + female 1139.8s = **2218.6s ≈ 37.0 分钟**。
|
||
|
||
双性别资产先例:`audio/voice/<gender>/<id>.{ogg,m4a}` 整目录切换 + 平铺回退(audio.js `_voiceCandidates`),设置项 `voice`(settings.js,枚举校验 + localStorage + 服务器 `_settings` 同步)——**input 变体目录可完整复用这套机制**。
|
||
|
||
### 1.5 测试管线现状(web/harness/live/)
|
||
|
||
- 手势注入:control.mjs `/gesture` → cdp.mjs `Input.dispatchTouchEvent`(touchStart/Move/End 序列,swipe 用 8 步插值 200px 位移满足 64px+400px/s 判定)。**没有键盘通道**。
|
||
- 注入同时打标 `__live.events.push({ev:'gesture', type})` 供审计。voice 验收对照 `web/harness/voice_text.json`(8 游戏期望文本)——**语音资产任何措辞变更都要同步此文件**,否则 ASR 相似度判定失真。
|
||
|
||
---
|
||
|
||
## 2. 键位方案(含业界依据)
|
||
|
||
### 2.1 业界惯例证据
|
||
|
||
**audiogames.net 社区(盲人音频游戏事实标准社区)**——数据库各游戏控制描述呈现出稳定模式:方向键=方位/移动([Night of Parasite](http://fog.audiogames.net/db.php?action=view&id=Night%2520of%2520Parasite)「arrow keys represent the four compass directions」、[AudioSpeed](http://fog.audiogames.net/db.php?action=view&id=AudioSpeed) arrows to steer、[Rebound](http://fog.audiogames.net/db.php?action=view&id=Rebound) 左右方向键移动);**空格=第一主操作**([Technoshock](http://fog.audiogames.net/db.php?action=view&id=technoshock) space 射击、[PacMan Talks](http://fog.audiogames.net/db.php?action=view&id=pacmantalks) space 查距离);回车=菜单确认;**"按住"与"连按两下"是既有表达**(论坛档案「I: fire your laser, hold down for continuous fire」「F: press twice to reactivate」——[audiogames.net 论坛存档](https://audiogames7.rssing.com/chan-63749684/all_p5.html))。
|
||
|
||
**A Blind Legend**(首批纯音频动作冒险,双端:移动端滑动手势 / PC Steam 键盘)——[Steam 社区键位参考](https://steamcommunity.com/sharedfiles/filedetails/?id=3192446890):←/→ 旋转、↑ 前进(按住持续)、**空格呼叫向导**、Esc 菜单、回车确认。它是"同一游戏、触屏手势与键盘两套措辞并行"的直接先例(移动端教学说的是手势,PC 版说的是按键)。
|
||
|
||
**Blind Drive**(音频游戏奖项得主,双端)——[官网](https://blinddrivegame.com/)自称「2-button arcade gameplay」,PC 版键盘(+手柄)只做左右闪避([Steam 页](https://store.steampowered.com/app/1300600/Blind_Drive/)、[评测](https://traxion.gg/blind-drive-review/))——证明音频游戏在 PC 上会把触屏/体感操作压缩成极简键盘映射。
|
||
|
||
**结论**:音频游戏键盘惯例 = **方向键表方位、空格表主操作、回车表确认/第二操作、按住表持续**。SoundGame 的 6 手势可以无损映射进这个惯例。
|
||
|
||
### 2.2 推荐键位表(主方案)
|
||
|
||
| 手势(触屏语义) | 键盘主键 | 备用 | 说明 |
|
||
|---|---|---|---|
|
||
| 左滑 / 右滑 | ← / → | A / D | keydown 即发,**零延迟**(无距离/速度判定) |
|
||
| 上滑 / 下滑 | ↑ / ↓ | W / S | 同上 |
|
||
| 单击 | 空格 | — | 走现有 tap 状态机:**快速点按**=tap(保留 ~300ms 双击窗延迟,与触屏同感);immediateTap 游戏(rhythm)按下即响 |
|
||
| 双击 | **回车** | 空格连按两下 | 专用键**即时**触发(不等 300ms 窗);空格×2 走状态机也能判出 doubletap(第一次的 pending tap 被取消,行为与触屏双击完全一致) |
|
||
| 长按 | **按住空格 ≥450ms** | — | 松开=longpressend;voice 播放中即旁白 1.7x 快进(复用 window 事件) |
|
||
|
||
设计要点与理由:
|
||
|
||
1. **双击给专用键(回车)而不是只靠连按空格**——这是本项目最重要的键位决策。若键盘只允许"空格×2",单击就必须等 300ms 双击窗(gesture.js 文件头自己写着这是要避开的坑)。回车=双击让"确认类"操作即时响应(A Blind Legend 回车=confirm 同构);同时保留空格×2 兼容,触屏肌肉记忆用户无学习成本。
|
||
2. **长按复用空格**而非另设键:长按的语义就是"按住那个确认键",且 audio.js 快进通道长按期间持续生效,独立键反而多一个要教的键位。
|
||
3. **WASD 作别名默认开启**:单手党/游戏手玩家习惯;不与箭头冲突,成本为零。
|
||
4. 不映射 Esc(下滑已覆盖菜单;Esc 留给未来退出全屏),不占 F 键/数字键(避开浏览器与读屏快捷键域)。
|
||
|
||
### 2.3 键位冲突分析
|
||
|
||
- **浏览器默认行为**:箭头键与空格会滚动页面、回车在焦点元素上触发 click——全部在 keydown 里 `preventDefault()` 解决(页面本就 `overflow:hidden` + `touch-action:none`,见 style.css;preventDefault 是双保险,[MDN keyboard-navigable widgets](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Keyboard-navigable_JavaScript_widgets) 的标准做法)。
|
||
- **表单目标守卫**:游戏页无输入框,但大厅有(用户名/PIN `<input>`)——键盘层遇 `INPUT/TEXTAREA/SELECT/contenteditable` 目标直接放行不拦截,避免大厅打不了字。
|
||
- **读屏软件**:NVDA/争渡在"浏览模式"会先吃掉箭头键([Stack Overflow: Screen Reader Blocks Keyboard Events](https://stackoverflow.com/questions/72538936/web-development-screen-reader-blocks-keyboard-events))。本项目游戏页无 DOM 文本可浏览,惯例解法是遮罩/根元素加 `role="application"` 促使读屏直通按键——列为实现时的验证项,不是阻塞项(本项目视障玩家主要在手机端,PC 视障玩家属加分场景)。
|
||
- **与游戏内快捷键**:现有游戏无键盘快捷键,无冲突。
|
||
|
||
---
|
||
|
||
## 3. 引擎适配设计(文件级)
|
||
|
||
### 3.1 总体思路:并行通道,不做先抽象后重写
|
||
|
||
在 `attachGestures()` 内**并列**增加键盘通道,与 touch/mouse 共享同一状态机与回调——不做"统一 InputChannel 抽象层"(323 行引擎重写风险大、收益为零;现有三通道已经证明并列式可行)。
|
||
|
||
### 3.2 gesture.js:新增键盘通道(预计 ~120 行)
|
||
|
||
```
|
||
attachGestures(element, onGesture, config) 内新增:
|
||
|
||
KEY_MAP = { ArrowUp:'up', ArrowDown:'down', ArrowLeft:'left', ArrowRight:'right',
|
||
KeyA:'left', KeyD:'right', KeyW:'up', KeyS:'down' } // WASD 别名
|
||
TAP_KEY = 'Space'; DBL_KEY = 'Enter';
|
||
|
||
window.addEventListener('keydown', onKeyDown) / 'keyup', onKeyUp) // detach 时移除
|
||
|
||
onKeyDown(e):
|
||
1. e.repeat → return // 方向键 repeat 抑制(按住↑只发一次 up)
|
||
2. e.target 是表单元素 → return // 大厅输入框放行
|
||
3. 仅拦截映射键;其余键不动 // 不吞 Tab(无障碍:别做键盘陷阱)
|
||
4. e.preventDefault() // 抑制滚动/焦点激活
|
||
5. tracking 时 return // 单飞行指针语义(与现有多指忽略一致,
|
||
// 见 onTouchStart 的 e.touches.length>1 分支)
|
||
6. 方向键 → vibrate('swipe'); onGesture(dir) // 即时,无位移判定
|
||
回车 → vibrate('confirm'); onGesture('doubletap') // 即时,同时 cancelPendingTap()
|
||
空格 → handleDown(centerX, centerY) // 进状态机:起 450ms 长按定时器;
|
||
// immediateTap 游戏立即发 tap
|
||
onKeyUp(e):
|
||
空格 → handleUp(centerX, centerY) // 复用收尾:长按→longpressend;快按→tap/双击窗
|
||
// (连按两下空格自然判 doubletap)
|
||
```
|
||
|
||
实现细节:
|
||
|
||
- **坐标取元素中心**(`getBoundingClientRect()` 缓存),永不 move——键盘路径必然只走 tap/longpress/doubletap 分支。
|
||
- **450ms 长按判定**:完全复用 `handleDown` 里的 `longPressTimer`,无新逻辑。
|
||
- **300ms 双击窗**:完全复用 `lastTapTime/pendingTapTimer`,键盘空格与触摸 tap 共享窗口(混合设备下"触屏点一下+空格点一下"也会判双击——语义正确:同一虚拟指针)。
|
||
- **互斥**:`tracking` 标志天然互斥(touch start 或 Space down 先到先得),与现有多指忽略语义一致,不需要 per-channel 状态。
|
||
- **焦点**:键盘事件挂 `window`(键盘事件目标是焦点元素而非手势根;游戏页焦点在 body,冒泡到 window 必达)。
|
||
- **通道来源标记(可选但建议)**:onGesture 前记 `lastInputChannel='key'|'touch'|'mouse'` 并导出 `getActiveChannel()`——§4 提示自适应与 §6 测试都要用。审计事件 `soundgame:input`(CustomEvent)亦可,供 harness 打标。
|
||
|
||
### 3.3 audio.js:showStartOverlay 键盘解锁 + 自适应文案(~20 行)
|
||
|
||
```js
|
||
// 键盘解锁:回车/空格(keydown 属 user activation,同步发起 resume 即可)
|
||
const onKey = (e) => { if (e.code === 'Enter' || e.code === 'Space') { e.preventDefault(); onTap(); } };
|
||
overlay.addEventListener('keydown', onKey);
|
||
// (overlay 需 tabindex="0" 并 focus(),keydown 才会落在它上面;或直接挂 window,
|
||
// resolve 后移除监听)
|
||
```
|
||
|
||
- 文案自适应:`text` 参数缺省时按 `isTouchLikely()`(§4.1)选「轻触开始」/「按回车或点击开始」;显式传 text 的调用照旧(全部 10 页现在都显式传 `'轻触开始'`——改成不传即自动,属可选的各页 1 行清理,不传也能跑)。
|
||
|
||
### 3.4 settings.js + 大厅:新增 `input` 设置(~25 行)
|
||
|
||
- `input: 'auto' | 'touch' | 'key'`(默认 `auto`),进 VALID 枚举;`_settings` 服务器同步机制零改动自动带上。
|
||
- 大厅设置面板加一个 `<select>`(自动 / 触屏优先 / 键盘优先)。
|
||
- 语义:`auto` = 三通道全开 + 提示跟随最近使用的通道;`touch`/`key` 只影响**提示文案与语音变体选择**,**不关闭另一通道**(永远双通道在线,混合设备友好,见 §5)。
|
||
- footer 文案随 input/检测切换。
|
||
|
||
### 3.5 游戏代码改动边界
|
||
|
||
| 项 | 改动 |
|
||
|---|---|
|
||
| 8 个游戏 game.js 的 onGesture 逻辑 | **零改动**(7 种事件语义不变) |
|
||
| rhythm immediateTap | **零改动**(空格按下即 kick,长按=双通道一致) |
|
||
| 旁白长按快进 | **零改动**(window 事件照发) |
|
||
| 游戏页 index.html | 可选各 1 行(遮罩 text 改自适应);不改也能跑 |
|
||
| gesture.js / audio.js / settings.js / 大厅 index.html | 引擎侧统一改(上述 ~185 行) |
|
||
|
||
---
|
||
|
||
## 4. 提示适配机制
|
||
|
||
### 4.1 平台检测:能力检测 + 运行时跟随,不做一次性硬判
|
||
|
||
依据:[MDN @media/pointer](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/pointer)、[@media/any-pointer](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/any-pointer)、[web.dev Interaction](https://web.dev/learn/design/interaction)、[CSS-Tricks 对混合设备的警告](https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/)。
|
||
|
||
- **初始值**:`isTouchLikely() = navigator.maxTouchPoints > 0 && matchMedia('(pointer: coarse)').matches`——触屏手机=true;纯桌面=false;**触屏笔记本**(pointer: fine 主指针 + 有触屏)=false(初始按键鼠提示,运行时纠正)。
|
||
- **运行时跟随**:gesture.js 记录 `lastInputChannel`;收到键盘事件后本会话提示切到键盘版,收到触摸切回。混合设备无需玩家配置即可用对提示(settings.input 手动覆盖只是兜底)。
|
||
- **手动覆盖**:settings.input(§3.4)。
|
||
|
||
### 4.2 画面提示文案表
|
||
|
||
| 位置 | 触屏版 | 键盘版 |
|
||
|---|---|---|
|
||
| 启动遮罩(audio.js) | 轻触开始 | 按回车或单击开始 |
|
||
| 大厅 footer(index.html) | 操作方式:上 / 下 / 左 / 右滑动、单击、双击 | 操作方式:方向键移动、空格单击、回车双击、按住空格长按 |
|
||
| 手势教学文案(游戏内 HTML,目前无) | 向左滑 | 按 ← 键 |
|
||
| 大厅 footer 第二行(300ms 延迟说明) | 保留 | 键盘版改为「回车=双击即时;空格单击有约 300ms 延迟(与手机一致)」 |
|
||
|
||
实现:一个 `inputHints.js` 小工具(~40 行)导出 `hintTable()` 按当前通道返回文案对象,audio.js 与大厅共用;CSS 侧如需图标/布局差异走 `@media (pointer: coarse)`。
|
||
|
||
### 4.3 语音提示三路线对比(核心决策)
|
||
|
||
先回答调研问题:**视障用户在 PC 上听「向左滑」会困惑吗?** 会,但程度有限且不该依赖它。audiogames.net 社区是 PC 键盘文化的大本营(键盘是默认、触屏是移动端补充);盲人社区对"手势↔键盘等价命令"的映射有明确预期——[AppleVis: Braille Display Users Deserve Better From Blindfold Games](https://applevis.com/blog/braille-display-users-deserve-better-blindfold-games) 明确批评过"只在触屏上讲手势、不给键盘等价命令"的做法。NVDA 用户知道触屏手势存在键盘等价物这个**概念**,但游戏语音说「向左滑」时,键盘玩家必须自己完成一次"向左滑≈按←"的翻译——**首次听不懂、需要猜**,违背本项目「开场交互教学」理念(教学句本身就在这 153 句里,如 storyline help 全句是操作说明)。学术侧同样有「默认键位不合用户习惯是音频游戏主要可用性问题」的记录([Design Guidelines for Audio Games](https://www.academia.edu/61440259/Design_Guidelines_for_Audio_Games))。**路线 c 不能作终态。**
|
||
|
||
#### 路线 a:双套语音资产(input 变体目录,像双性别一样)
|
||
|
||
机制:`voice/<gender>/key/<id>.{ogg,m4a}`,audio.js `_voiceCandidates` 追加一跳(key 目录 → gender 目录 → 平铺回退),id 集合只需覆盖 153 句(其余回退现有资产,不存在双份)。
|
||
|
||
| 维度 | 评估 |
|
||
|---|---|
|
||
| 资产成本 | **新增 153 句 × 2 性别 = 306 个 TTS 生成**(措辞改写:「向左滑」→「按左方向键」、「单击屏幕」→「按一下空格」、「双击」→「按回车」);落盘 612 文件(ogg+m4a)、**+49.4 MiB** 仓库增量;新音频 **37.0 分钟**(实测同 id 现有资产时长,措辞微调后误差 <10%) |
|
||
| GPU 时长 | Qwen3-TTS 本地串行(flock)。参照系:R1 全量双性别管线 286 文件/43.5 分钟音频一个晚间会话完成(docs/audio/round1-report.md);本路线 306 文件/37 分钟与之同量级——**单晚 GPU 串行一轮 + --check 复检 + 少量离群重跑,≤ 半个 GPU 晚**。管线零改动支持:台词表加 `(fid, instruct, text, input='key')` 维度或直接新增 `*_KEY_LINES` 表,断点续跑/--only/--check/--convert 全部照用 |
|
||
| 体验 | 最优:两端教学语气精确(「单击屏幕,继续剧情」→「按一下空格,继续剧情」,指令动词与输入设备严格一致) |
|
||
| 风险 | ①153 句键盘措辞要策划逐句改写+试听(与 b 相同);②台词表长期 ×2 维护(改一句要过两个变体);③voice_text.json 需按 input 变体分集(live 管线 session 加 input 字段选期望文本集);④旧资产不动、回退安全,**增量落地可灰度** |
|
||
|
||
#### 路线 b:平台无关措辞重录(一套通吃)
|
||
|
||
机制:153 句全部改成不含输入方式的措辞(「向左滑:先检查收音机」→「向左:先检查收音机」),**替换**现有 306 文件。
|
||
|
||
| 维度 | 评估 |
|
||
|---|---|
|
||
| 资产成本 | 同样 **306 个 TTS 生成**(替换而非新增),37 分钟音频重录,仓库体积持平(措辞略短或 -5%);GPU 时长与 a 完全相同 |
|
||
| 额外成本 | ①**已验收资产全部作废重验**(8 游戏 R1~R6 轮次里这 153 句的试听结论失效);②voice_text.json 153 句期望文本全量重对齐;③逐句审查"去操作词后语义仍完整"(如 storyline e2「单击屏幕,重新开始」→「单击,重新开始?」教学力度弱化) |
|
||
| 体验 | 两端通用但**两端都变抽象**:触屏新手失去"滑"这个动词(本项目核心新手群体恰是手机端),键盘玩家仍要做一次"单击=空格"的映射(单击一词对键盘玩家仍非按键名)。属于两端各让一步 |
|
||
| 风险 | 替换式落地无法灰度;ASR 对照/多音字清单/验收记录全要重跑 |
|
||
|
||
#### 路线 c:语音不动,PC 靠画面文字提示
|
||
|
||
| 维度 | 评估 |
|
||
|---|---|
|
||
| 成本 | 零 TTS、零资产改动;只有 §4.2 的画面文案工作 |
|
||
| 体验 | 纯音频游戏玩家不看画面(本项目立项前提),PC 视障玩家**听到的仍是「向左滑」**,见上文社区证据——键盘等价命令缺失是被盲人社区明确批评过的可及性缺陷 |
|
||
| 定位 | **只允许作为 MVP 过渡态**(PC 先上线、语音资产下一轮补),且要在 PC 版开场遮罩/大厅用文字+后续一句通用语音说明「键盘操作:方向键/空格/回车」兜底 |
|
||
|
||
#### 三路线速比
|
||
|
||
| | a 双套 | b 重录通用 | c 不动语音 |
|
||
|---|---|---|---|
|
||
| TTS 生成量 | 306 文件 / 37min | 306 文件 / 37min | 0 |
|
||
| 仓库增量 | +49.4 MiB | ~0 | 0 |
|
||
| 策划文案工作 | 153 句新写 | 153 句改写+全量重审 | 0 |
|
||
| 触屏端体验 | 不变(原资产不动) | 轻微弱化 | 不变 |
|
||
| PC 端体验 | 最优 | 中 | 差(视障不可用级) |
|
||
| 落地方式 | 增量可灰度 | 替换不可灰度 | — |
|
||
| 长期维护 | 台词表 ×2 | 台词表 ×1 | ×1(欠账) |
|
||
|
||
**推荐 a**:与 b 成本几乎相同但风险低一档(不动已验收资产、可灰度、触屏端零感知),且两端教学语气都保住精确性;机制上完整复用 male/female 双性别目录的成熟先例(引擎解析、管线、QC、设置项全套现成)。
|
||
|
||
---
|
||
|
||
## 5. 混合设备(触屏笔记本 / 平板外接键盘)
|
||
|
||
- **运行策略:三通道永远并行在线,不做模式切换。** gesture.js 的 `tracking` 单飞行指针语义天然防跨通道干扰(触摸进行中空格 down 被忽略,反之亦然——与现有多指忽略一致)。
|
||
- **提示跟随最近通道**(§4.1 运行时跟随):玩家把平板接上键盘敲了一下方向键,遮罩外的教学提示文案本会话即切键盘版;摸了屏又切回。无需重启页面、无需设置项。
|
||
- **语音变体**(路线 a 落地后)同理跟随 `lastInputChannel`;settings.input='auto' 之外的手动值仅用于"我知道我在用什么,别自动切"的用户。
|
||
- **一个已知边界**:混合设备上"触摸单击 + 键盘空格单击"共享同一个双击窗(同一虚拟指针),300ms 内跨通道两下会判 doubletap——语义上合理(玩家意图就是双击),文档写明即可,不做隔离。
|
||
|
||
---
|
||
|
||
## 6. 测试影响(playtest / live 管线)
|
||
|
||
- **live 管线加键盘通道(建议,~30 行)**:control.mjs `/gesture` 接受 `{"type":"keyup","code":"ArrowLeft"}` 之类或新增 `/key` 端点 → cdp.mjs 增加 `Input.dispatchKeyEvent`(rawKeyDown/keyUp;比 `Runtime.evaluate` 派发合成 KeyboardEvent 更拟真——不过 gesture 层挂 window、合成事件也够用,CDP 通道留作高拟真档)。审计事件沿用 `{ev:'gesture', type}` 打标,附 `source:'key'`。
|
||
- **解锁路径必须测键盘版**:会话第一步 tap 解锁遮罩(现状 README 明确"第一个动作必须是 tap");键盘通道上线后补一条"第一个动作 = Enter 解锁"的用例,验证 keydown 路径的 AudioContext resume。
|
||
- **双通道回归**:现有触摸用例零改动可跑(键盘层不改变触摸路径);新增少量键盘用例重点覆盖:方向键 repeat 抑制(按住 1s 只发一次)、空格 300ms 双击窗(快按两下=doubletap、慢按=tap)、按住 450ms=longpress+旁白快进(`__live` 的 boost 事件)、大厅输入框打字不被拦截。
|
||
- **voice_text.json**:路线 a 落地时按 input 变体分集(session settings 已支持传 `voice/speed/bgm/guide`,加 `input` 字段即可);路线 b 则要全量重对齐——这也是推荐 a 的测试侧理由。
|
||
- **playtest 脚本(sessions/)**:若某轮 playtest 主题是"PC 玩家体验",直接用键盘通道跑既有剧本即可,无需新 harness。
|
||
|
||
---
|
||
|
||
## 7. 推荐组合与分期
|
||
|
||
### MVP(最小可用,PC 能完整玩全部 8 游戏)——约 1~2 人日
|
||
|
||
| 改动 | 文件 | 量级 |
|
||
|---|---|---|
|
||
| 键盘输入通道(含 repeat 抑制/表单守卫/滚动抑制/通道标记) | web/engine/gesture.js | ~120 行 |
|
||
| 遮罩键盘解锁 + 自适应默认文案 | web/engine/audio.js | ~20 行 |
|
||
| input 设置项(auto/touch/key) | web/engine/settings.js + web/index.html | ~25 行 |
|
||
| 大厅 footer 文案切换 + inputHints 工具 | web/index.html(+新 inputHints.js ~40 行) | ~45 行 |
|
||
| live 管线键盘通道 + 解锁/双通道用例 | web/harness/live/{control,cdp}.mjs | ~30 行 |
|
||
|
||
语音此时不动(路线 c 过渡态):PC 遮罩文字提示写明「键盘:方向键/空格/回车/按住空格」,教学语音里的「向左滑」暂由玩家自行映射——**过渡期建议不超过一个语音资产轮次**。
|
||
|
||
### 完整版(语音双端精确)——策划 153 句键盘措辞 + 半个 GPU 晚 + 工程半日
|
||
|
||
1. 策划:153 句键盘版措辞改写(台词表新增 key 变体行,参照 15/16 号 voice-ids 文档格式交付);
|
||
2. 资产:gen_voice_dual.py 加变体维度,`--game all` 串行一轮(306 生成/612 落盘/+49.4 MiB),`--check` 复检;
|
||
3. 工程:audio.js voice 解析加 key 目录跳(~15 行)、harness voice_text.json 分集(~20 行);
|
||
4. 验收:live 管线 `input:'key'` 会话跑 8 游戏开场教学段,ASR 对照键盘版文本。
|
||
|
||
### 依赖与顺序
|
||
|
||
MVP 与语音路线完全解耦,可先行;完整版的变体目录机制依赖 MVP 的 `lastInputChannel`(决定加载哪套语音)。混合设备体验在 MVP 即达标(双通道并行+提示跟随),完整版补齐语音一致性。
|
||
|
||
---
|
||
|
||
## 附:本文数字复现方式
|
||
|
||
- 台词/操作词统计:`tools/gen_voice_dual.py` 各 `*_LINES` 表,regex `[滑|单击|双击|长按]`(2026-09-09 实测 153/651 句);
|
||
- 资产时长/体积:对受影响 306 个 .ogg ffprobe 求和(male 1078.8s / female 1139.8s),文件级 du 合计 49.4 MiB(612 文件含 .m4a);
|
||
- 业界链接均已在 §2/§4 正文内嵌。
|
||
|
||
## 附:业界依据来源
|
||
|
||
- [A Blind Legend — Steam 社区键位参考](https://steamcommunity.com/sharedfiles/filedetails/?id=3192446890)、[Steam 商店页](https://store.steampowered.com/app/437530/)
|
||
- [Blind Drive 官网](https://blinddrivegame.com/)、[Steam 页](https://store.steampowered.com/app/1300600/Blind_Drive/)、[Traxion 评测](https://traxion.gg/blind-drive-review/)、[PCGamingWiki](https://www.pcgamingwiki.com/wiki/Blind_Drive)
|
||
- audiogames.net 数据库:[Night of Parasite](http://fog.audiogames.net/db.php?action=view&id=Night%2520of%2520Parasite)、[Technoshock](http://fog.audiogames.net/db.php?action=view&id=technoshock)、[PacMan Talks](http://fog.audiogames.net/db.php?action=view&id=pacmantalks)、[Rebound](http://fog.audiogames.net/db.php?action=view&id=Rebound)、[AudioSpeed](http://fog.audiogames.net/db.php?action=view&id=AudioSpeed)、[论坛存档](https://audiogames7.rssing.com/chan-63749684/all_p5.html)
|
||
- [AppleVis — Braille Display Users Deserve Better From Blindfold Games(手势↔键盘等价命令)](https://applevis.com/blog/braille-display-users-deserve-better-blindfold-games)
|
||
- [MDN — @media/pointer](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/pointer)、[@media/any-pointer](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/any-pointer)、[Keyboard-navigable JavaScript widgets](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Keyboard-navigable_JavaScript_widgets)
|
||
- [web.dev — Interaction](https://web.dev/learn/design/interaction)、[CSS-Tricks — Interaction Media Features 混合设备陷阱](https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/)
|
||
- [Stack Overflow — Screen Reader Blocks Keyboard Events](https://stackoverflow.com/questions/72538936/web-development-screen-reader-blocks-keyboard-events)
|
||
- [Design Guidelines for Audio Games](https://www.academia.edu/61440259/Design_Guidelines_for_Audio_Games)
|