Files
blind/林夏.md
T
xslandClaude Opus 4.6 d03bcb14e4 v2收尾:手势升级 + 暂停退出 + 结局语音化 + 分支补全 + 12条新音频
gestures.js: 新增长按(1s)、两指长按(暂停)、三指点击(退出),支持多指检测
engine.js: REPLY_COUNT变量、长按重播/状态广播、暂停恢复退出机制、
  选择提示音、结局台词预录音频替换TTS、内容预警+心理热线
story.js: MSG-10/11 follow-up补全down(沉默)选项
index.html: 操作指南添加长按/安全手势、内容预警文字
batch_tts_voxcpm.py: 新增12条台词(结局旁白+系统提示+分支NPC反应)
audio/mp3/tts: 12个VoxCPM2生成的林夏声线MP3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 23:22:04 +08:00

46 KiB
Raw Blame History

林夏

类型:情感 / 叙事 / 轻悬疑 · 纯音频交互游戏 目标用户:20–25 岁年轻女性(大学生 / 初入职场 / 一线城市独居) 时长:约 3045 分钟 核心隐喻:所有人都给你发语音,没有人发文字。今晚你必须一条一条听完,才能决定明天的自己长什么样。 核心原则:每次选择都必须产生玩家能听到的、不同的后果。


0. 一句话概述

24 岁的林夏,被分手、被裁员、被妈妈催婚,全发生在同一个周五。她把手机调成静音、躲进合租屋,关上灯。屏幕坏了——只剩耳机能听、手指能摸。今晚会有 16 条电话和语音陆续到达。她接谁、不接谁、怎么回——每一个回应都会被对方听到,每一段对话都会走向不同的结局。


1. 玩家与角色

1.1 玩家是谁

  • 林夏,24 岁,西部小城来的姑娘,北漂第 3 年。
  • 国贸某外企新媒体专员,今天上午刚被裁员(合同到期不续签)。
  • 与男友"阿哲"恋爱 2 年 4 个月,今天下午刚被分手("我们性格真的不合")。
  • 与一位大学闺蜜"小美"合租,但小美今晚去了酒吧。
  • 老家妈妈让她"五一回来相亲"。
  • 微信未读:99+。
  • 屏幕:今天上午地铁挤碎了的那块屏,现在显示一片黑——但触控、震动、声音都还在。

1.2 林夏不是"听者"——她会说话

与传统音频叙事游戏不同,林夏有自己的声音。玩家的每次选择都会触发林夏的一句语音回应,对方会听到并做出反应。这构成了完整的对话:

NPC 说话 → 玩家选择方向 → 林夏说出回应 → NPC 对林夏的回应做出反应

林夏的声音设计:疲惫但清醒、克制但有温度、年轻但不幼稚。


2. 核心玩法

2.1 七种手势——零视觉依赖

手势 全局含义 情感色彩 触觉反馈 音效
单击 接受 / 回应 / 继续 中性应答 轻触感 清脆短音 880Hz
双击 跳过 / 忽略 不耐烦 / 赶时间 短促双震 下行滑音 587→392Hz
长按(1s 根据状态:重听或播报 确认 缓慢长震 低沉确认音 330→440Hz
右滑 温暖 / 靠近 接纳、关心 流畅长震 上行音 440→659Hz
左滑 冷淡 / 推开 疏离、敷衍 干脆短震 下行音 523→349Hz
上滑 好奇 / 追问 想了解更多 快速上弹震 快速上行 523→784Hz
下滑 沉默 / 退缩 说不出口 极轻慢震 极轻低音 330Hz

不需要记位置。每个方向有固定的情感含义,贯穿全游戏。右滑永远是温暖,左滑永远是冷淡。

长按随时可用:任何状态下长按 1 秒,系统根据当前状态做出响应——在 playingchoosing 状态下重播上一句 NPC 语音,其他状态下语音播报当前位置(例如"小美的消息,等待回复")。这是玩家在纯音频环境下的"安全网"。

2.2 对话流程(多轮交互)

 ┌─────────────────────────────────────────────────┐
 │  [提示音 + 震动]                                  │
 │  消息到达                                         │
 │  → 单击播放 / 双击跳过                             │
 └──────────┬──────────────────────────────────────┘
            ▼
 ┌─────────────────────────────────────────────────┐
 │  NPC 语音播放(Round 1                          │
 │  "你被甩了我都听说了,明天陪你喝酒"                  │
 └──────────┬──────────────────────────────────────┘
            ▼
 ┌─────────────────────────────────────────────────┐
 │  ★ 进入选择态                                     │
 │  [三音上行提示音 + 明确震动模式(长-短-长)]          │
 │  系统语音提示:"等待回复"                            │
 │  玩家操作:右滑 / 左滑 / 上滑 / 下滑 / 单击         │
 │  长按 → 重听 NPC 刚才说的话                        │
 └──────────┬──────────────────────────────────────┘
            ▼
 ┌─────────────────────────────────────────────────┐
 │  [打字音效 ~0.8s → 发送音效]                       │
 │  林夏的语音播放                                    │
 │  右滑 → "好啊,明天不见不散。"                       │
 │  左滑 → "再说吧。"                                │
 │  上滑 → "你怎么知道的啊?"                          │
 │  下滑 → [一秒沉默]                                │
 │  单击 → "嗯。"                                    │
 └──────────┬──────────────────────────────────────┘
            ▼
 ┌─────────────────────────────────────────────────┐
 │  NPC 反应播放(Round 2)—— 因选择而异               │
 │                                                   │
 │  右滑: "行!那我叫上安安!老地方见!"                 │
 │  左滑: "……好吧,你休息。"                           │
 │  上滑: "还不是阿哲那个大嘴巴告诉我的……"              │
 │  下滑: "??你还活着吗哈哈。算了不打扰你了"           │
 │  单击: "嗯是什么意思啊哈哈!行吧你休息"               │
 └──────────┬──────────────────────────────────────┘
            ▼
 ┌─────────────────────────────────────────────────┐
 │  [可选] Round 3: 继续对话(仅部分路径触发)          │
 │  NPC 追问 → 再次选择 → 林夏回应 → 对话结束           │
 └──────────┬──────────────────────────────────────┘
            ▼
 ┌─────────────────────────────────────────────────┐
 │  → 等待 → 下一条消息到达                            │
 └─────────────────────────────────────────────────┘

2.3 关键设计

方面 说明
林夏的声音 每次选择后都有语音回应(或有感知的沉默)
NPC 反应 根据选择播放完全不同的反应语音
选择的感受 五个方向各自导向不同的音频内容
对话长度 温暖/追问路径更长、信息更多;冷淡/沉默路径更短
独家信息 追问路径可获得其他路径听不到的剧情细节
玩家驱动 没有时间限制,消息不会自动推进,完全由玩家操作控制节奏
双击跳过 在林夏回应或 NPC 反应播放中,双击可跳过当前语音,加速流程

2.4 关键机制:「正在输入…」

当林夏回应前,耳机里会播放微弱的键盘敲击声 → 发送音效 → 然后才播放林夏的语音。这 ~1 秒的"打字等待"让选择感觉真实——你不是在"选菜单",你是在"发语音"。


3. 消息全列表(16 条)

# 来源 形式 关键内容 有无选择
01 妈妈 电话 "今晚煮了你爱吃的排骨" 接听 → 无选择(自动回应)
02 闺蜜小美 语音 ×3 "你被甩了我都听说了,明天陪你喝酒" 5 方向 + 追问 Round 3
03 外卖小哥 电话 "你的麻辣烫到了" 接听 → 无选择(自动回应)
04 前任阿哲 语音 "我的 AirPods 落你那了" 5 方向 + 温暖/追问各有 Round 3
05 HR 王姐 语音 "离职流程周三前办完就行" 5 方向
06 室友安安 语音 "我在 Helens!快来!" 5 方向
07 寝室群 语音 ×5 室友 A 升职,@林夏 5 方向 + 温暖 Round 3
08 未知号码 电话 呼吸声 6 秒后挂断 无选择(悬疑钩子)
09 小美(醉) 语音 ×4 "阿哲大三追过我" 5 方向 + 追问 Round 3
10 妈妈 文字(系统语音朗读) "睡了吗?" 5 方向 + 温暖/追问各有 Round 3
11 阿哲 电话 "我能过去拿吗?就 5 分钟" 5 方向(关键分支锚点)
12 周南 语音 "初三坐你后桌那个" 5 方向
13 周南 语音 ×4 "今天我生日" 5 方向 + 温暖 Round 3
14 妈妈 电话 "你不接电话我有点担心" 接听 → 自动回应,条件分支
15 自己 系统通知 3 年前录的备忘录 单击播放 / 双击跳过
16 妈妈 长语音 ×8 妈妈 24 岁那年的故事 无选择(结局前奏)

4. 16 条消息详细设计

MSG-01 妈妈 · 电话

类型:来电(铃声 + 长震动) Round 1(接听后):妈妈说"今晚煮了你爱吃的排骨,你什么时候回来呀。" 接听后自动:林夏轻轻说"嗯……好。"(无选择) 不接 → 直接跳到下一条。

状态:接听 → MOM_LINK = true


MSG-02 小美 · 语音 ×3(教学关)

Round 1:小美连发三条语音:

  • "林夏!你还活着吗!"
  • "你被甩了我都听说了——别难过啊"
  • "明天陪你喝酒,去不去"

选择 + Round 2

方向 林夏说 小美反应 profile
右滑 "好啊,明天不见不散。" "行!那我叫上安安!老地方见!别放鸽子啊!" engagement+2
左滑 "再说吧。" "……好吧。你要是改主意了随时说。" avoidance+1
上滑 "你怎么知道的啊?" "还不是阿哲自己发朋友圈!你不知道吗?他发了又秒删……" engagement+1独家信息
下滑 [沉默 2s] "??你还活着吗哈哈。好了不烦你了,有事找我。" avoidance+2
单击 "嗯。" "嗯是什么意思啊哈哈!行吧你休息。"

追问 Round 3(上滑后,小美说完"他发了又秒删"):

  • 右滑 → 林夏:"他……发了什么?" → 小美:"就发了一张你们以前的照片,配了一句'值得'。两分钟就删了" → 独家信息
  • 其他 → 对话结束

MSG-03 外卖小哥 · 电话

类型:来电 Round 1:外卖小哥:"你好,你的麻辣烫到了,在楼下。" 接听后自动:林夏说"好,我下来拿。"(无选择) 不接 → 漏接。


MSG-04 阿哲 · 语音

Round 1:阿哲说"那个……我的 AirPods 好像落你那了,方便帮我寄一下吗?"

方向 林夏说 阿哲反应 效果
右滑 "你那边……还好吗。" "我……挺好的。" [停顿] "你呢?" engagement+2
左滑 "知道了。明天寄。" "嗯……谢了。" HE_BACK=false, avoidance+1
上滑 "你怎么不自己来拿。" "我……不太方便过去。" [欲言又止] engagement+2
下滑 [沉默 3s] [等 5 秒] "那……我晚点再说吧。" → 阿哲撤回消息 HE_BACK=null, avoidance+2
单击 "好。" "谢谢。" HE_BACK=false, engagement+1

右滑 Round 3(阿哲问"你呢?"后):

  • 右滑 → 林夏:"不太好。" → 阿哲:"……对不起。" [沉默]
  • 左滑 → 林夏:"挺好的。" → 阿哲:"那就好。"
  • 下滑 → [沉默] → 阿哲:"……那,晚安。"

上滑 Round 3(阿哲说"不太方便"后):

  • 上滑 → 林夏:"为什么。" → 阿哲:"我……" [长停顿] "没什么。你寄吧。" → HE_BACK=false
  • 右滑 → 林夏:"算了,不问了。" → 对话结束

MSG-05 HR 王姐 · 语音

Round 1:王姐说"小夏,离职流程周三前办完就行。别有压力。"

方向 林夏说 王姐反应 profile
右滑 "谢谢王姐。" "别客气。你还年轻,出去看看也好。这行业你知道的。" engagement+1
左滑 "好的,知道了。" [无反应,对话结束]
上滑 "王姐……有什么建议吗?" "你想听真话?别在这行耗了。你有本事,去试试别的。" → 独家 engagement+2
下滑 [沉默] [无反应,对话结束] avoidance+1
单击 "好。" [无反应,对话结束]

MSG-06 安安 · 语音

Round 1:安安在酒吧里大喊:"我在 Helens!快来!今天 happy hour"(背景很吵)

方向 林夏说 安安反应 profile
右滑 "在哪儿啊?我看看吧。" "对面那个!我给你留了位置!赶紧赶紧!" engagement+2
左滑 "不了,你们玩好。" "好吧!那我给你寄一杯!" [大笑]
上滑 "你喝了多少了?" "才两杯!我清醒得很!……好吧三杯。快来!" engagement+1
下滑 [沉默] "喂?信号不好吗?好吧以后再约!" avoidance+1
单击 "嗯。" "嗯是来还是不来啊!算了回头聊!"

MSG-07 寝室群 · 语音 ×5

Round 1:群消息连续播放:

  • 室友 A:"我升主管了!!!"
  • 室友 A"请你们吃饭!!"
  • 室友 B:"我的天!恭喜!!"
  • 室友 B"必须喝一个!"
  • 室友 C"@林夏 你也来吧"
方向 林夏说 群里反应 效果
右滑 "恭喜恭喜!太厉害了!" 室友 A:"哈哈谢谢!我请客!你最近怎么样啊?" GROUP_REPLY, engagement+2
左滑 "恭喜。" [群里继续聊别的,没人注意到你的冷淡] avoidance+1
上滑 "怎么升的啊?讲讲!" 室友 A:"说来话长!我们老板走了我就顶上了哈哈" → 独家 GROUP_REPLY, engagement+2
下滑 [沉默,不回群] [群里继续热闹,但渐渐没人 @ 你了] avoidance+2
单击 [发一个表情] 室友 B:"林夏发表情了哈哈" GROUP_REPLY

右滑 Round 3(室友 A 问"你最近怎么样"后):

  • 右滑 → 林夏:"挺好的~你请客我去!" → 室友们欢呼
  • 左滑 → 林夏:"还行。" → 室友 B:"你是不是有事啊?没事可以跟我们说"
  • 下滑 → [沉默] → 对话自然结束

MSG-08 未知号码 · 电话(悬疑)

类型:来电 接听后:只有呼吸声,6 秒后自动挂断。然后心跳音效。 无选择——悬疑钩子。 不接 → 未接来电。

回收:MSG-16 妈妈长语音第 5 段中提到"我外婆每天晚上都给我打一个电话,她不说什么"——暗示未知号码就是妈妈从另一个号码拨出的"不说什么的电话",与妈妈线形成跨代呼应。不做明确揭示,留给玩家自己领悟。如果玩家接了 MSG-08 + 听完 MSG-16,两段呼吸/沉默会在记忆中形成对位。


MSG-09 小美(醉)· 语音 ×4

BGM 切换为悬疑风格

Round 1(四段醉酒语音):

  • "林夏……我跟你说个事"
  • "阿哲那个人……大三的时候追过我"
  • "但我没答应他……因为我知道你喜欢他"
  • "我到现在都没跟你说……对不起"
方向 林夏说 小美反应 profile
右滑 "小美……你没事吧。" "我没事……我就是喝多了想告诉你……你别怪我。" [哭] engagement+2
左滑 "我知道了。" [沉默 3 秒] → 小美撤回了刚才的语音 avoidance+1
上滑 "你……为什么现在才说。" "因为你今天……我怕你以后恨我……" engagement+2
下滑 [沉默] [10 秒后] 小美撤回了刚才的语音 avoidance+2, nostalgia+1
单击 "嗯。" "你不生气吗?" [等 3 秒] "算了……当我没说" nostalgia+1

上滑 Round 3(小美说"怕你恨我"后):

  • 右滑 → 林夏:"我不恨你。" → 小美 [大哭]:"谢谢你……谢谢你林夏"
  • 左滑 → 林夏:"我得想想。" → 小美:"好……对不起" → 对话结束
  • 下滑 → [沉默] → 小美:"你还在吗?" → [长沉默] → 小美撤回

MSG-10 妈妈 · 文字消息

Round 1:消息到达提示音。系统语音朗读三个字——"睡了吗?"

方向 林夏说 妈妈反应 效果
右滑 "没有,怎么了?" 妈妈发语音:"没事,就是想起来问一下。今天怎么样?" MOM_LINK, engagement+2
左滑 "嗯,要睡了。" 系统语音朗读妈妈的文字:"好,早点睡。" MOM_LINK, avoidance+1
上滑 "妈……你能打电话给我吗?" [电话铃响] 妈妈打来了!接听后:"怎么了夏夏?" MOM_LINK, engagement+3
下滑 [不回复] [30 秒后] 系统语音朗读妈妈的文字:"看到了回一下。" avoidance+2
单击 "嗯。" 系统语音朗读妈妈的文字:"嗯就是睡了?晚安。" MOM_LINK

上滑路径(妈妈打来电话后,说"怎么了夏夏?你声音不对。"):

  • 右滑 → 林夏:"没什么……就是有点累。" → 妈妈:"累了就回家歇几天。妈煮了排骨。"
  • 上滑 → 林夏:"妈,我辞职了。" → 妈妈 [沉默 2 秒]:"辞了就辞了。回来再说。"
  • 下滑 → [沉默,只有呼吸] → 妈妈:"不想说就不说。妈在这呢。"

右滑路径(妈妈问"今天怎么样"后):

  • 右滑 → 林夏:"还好。" → 妈妈:"那就好。别太晚了。"
  • 上滑 → 林夏:"不太好。" → 妈妈:"怎么了?"
    • 右滑 → 林夏:"工作没了。" → 妈妈:"……回来歇几天吧。妈不催你。"
    • 下滑 → [沉默] → 妈妈:"不想说就不说。妈在这呢。" → 对话结束

MSG-11 阿哲 · 电话(关键分支)

类型:来电 Round 1(接听后):阿哲说"那个……AirPods 的事,我能过去拿吗?就 5 分钟。"

方向 林夏说 阿哲反应 效果
右滑 "可以。但只有 5 分钟。" "好,我马上过来。谢谢。" → [挂断] HE_BACK=true
左滑 "不方便。我明天寄给你。" "好吧……那谢了。" → [挂断] HE_BACK=false
上滑 "为什么非要今天来?" "我……明天要出差。" [停顿] "就 5 分钟。" → 追问后再选
下滑 [沉默 → 挂断音] [电话断了] HE_BACK=false
单击 "不了。" "好。" → [挂断] HE_BACK=false

上滑 Round 3(阿哲说"就 5 分钟"后,再次进入选择):

  • 右滑 → 林夏:"……来吧。" → 阿哲:"谢谢。我马上到。" → [挂断] → HE_BACK=true
  • 左滑 → 林夏:"还是算了。" → 阿哲:"好吧。" → [挂断] → HE_BACK=false
  • 下滑 → [沉默] → 阿哲:"那……你考虑一下,我等你消息。" → [挂断] → HE_BACK=false

如果 HE_BACK=true → 消息 12 之前插入门铃声 + 敲门声场景。


MSG-12 周南 · 语音

Round 1:周南说"林夏,是我,周南。初三坐你后桌那个。好久没联系了,今天突然想到你。"

方向 林夏说 周南反应 效果
右滑 "周南!你还记得我啊!" "当然记得。你是我们班最安静的那个。" [轻笑] ZHOUNAN_DEPTH+1, engagement+2
左滑 "嗯,你好。" "嗯……好久不见。你现在在哪呢?" ZHOUNAN_DEPTH+1, nostalgia+1
上滑 "你怎么有我微信的?" "你忘了吗?毕业那天你加的我。不过你可能早忘了哈哈。" ZHOUNAN_DEPTH+1, nostalgia+2
下滑 [沉默] "你是不是不记得我了?没关系的。就是突然想说声嗨。" nostalgia+1
单击 "嗯……好久不见。" "是啊,好久不见。" ZHOUNAN_DEPTH+1

MSG-13 周南 · 长语音 ×4

BGM 切换为周南主题(温暖吉他)

Round 1(四段语音):

  • "高考那年我没考好,去复读了"
  • "后来上了个二本,学了个不喜欢的专业"
  • "毕业回了老家,在我爸店里帮忙"
  • "今年刚来北京出差……今天是我生日"
方向 林夏说 周南反应 效果
右滑 "一个人在北京,还好吗?" "还行吧。习惯了。你呢?你在北京过得好吗?" ZHOUNAN_DEPTH+1, engagement+2
左滑 "你讲了好多。" "是吗?不好意思,话太多了。" [轻笑] ZHOUNAN_DEPTH+1, nostalgia+1
上滑 "……生日快乐。" [沉默 2 秒] "谢谢你。你是今天第一个跟我说的。" ZHOUNAN_DEPTH+1, ZHOUNAN_SHARE, nostalgia+2
下滑 [沉默] "不好意思突然说这么多……那,晚安。" avoidance+1
单击 "嗯,我都听到了。" "嗯。谢谢你听我说。" ZHOUNAN_DEPTH+1

右滑 Round 3(周南问"你在北京过得好吗"后):

  • 右滑 → 林夏:"不太好。今天也是我的坏日子。" → 周南:"那我们算是难兄难妹了。" [轻笑] → ZHOUNAN_SHARE
  • 左滑 → 林夏:"还行吧。" → 周南:"那就好。"
  • 上滑 → 林夏:"你为什么突然联系我?" → 周南:"因为……今天生日嘛,想起以前的事。想起你。"

MSG-14 妈妈 · 第二次电话

类型:来电 Round 1:妈妈说"夏夏,你不接电话我有点担心。你还好吗?"

接听后 → 林夏自动说"嗯,我没事。" 妈妈:"真的没事?"

  • 如果 MOM_LINK=true"好。那早点睡。妈爱你。" → 对话结束
  • 如果 MOM_LINK=false"你今天怎么都不理妈妈?出什么事了吗?" → 进入选择:
    • 右滑 → 林夏:"没事妈,就是手机坏了。" → 妈妈:"那你赶紧修啊。好了早点睡。" → MOM_LINK=true
    • 左滑 → 林夏:"嗯,没事。" → 妈妈:"……好吧。晚安。" → MOM_LINK=true
    • 下滑 → [沉默] → 妈妈:"你不说话我更担心。算了,不逼你了。晚安。" → MOM_LINK=true 不接 → MOM_LINK 保持当前值(可能仍为 false)

状态:接听 → MOM_LINK=true


MSG-15 时光胶囊 · 系统通知

特殊消息:系统提示音 + "3 年前的今晚,你给自己录过一段备忘录。"

  • 单击播放 → 根据玩家画像播放不同版本(avoidance / engagement / nostalgia → SELF_RECORD=true
  • 双击跳过 → avoidance+2

无选择交互。这条消息是独白时刻。


MSG-16 妈妈 · 长语音 ×8(结局前奏)

条件MOM_LINK=true 时播放

妈妈讲她 24 岁那年的故事:

  1. "夏夏。妈妈知道你今天电话不接。妈妈不催你,就是想跟你说一件事。"
  2. "你 24 了对吧。妈妈 24 那年——你应该没听我说过——"
  3. "那时候妈妈在广州,给一个香港老板做秘书,1995 年。那年我也被分手过。也被开除过。"
  4. "那年我哭得特别难看。后来呢?后来就没什么后来了。我回了老家,认识了你爸,生了你。"
  5. "我想跟你说,那年夏天我没死,是因为我外婆每天晚上都给我打一个电话。"
    • 音频设计:第 5 段开头,叠入与 MSG-08 相同的电话底噪质感(轻微线路嘶声),持续 2-3 秒后淡出。如果玩家之前接了 MSG-08 的未知号码,这一刻会在听觉上产生似曾相识的感觉——同样的电话底噪、同样的呼吸停顿,但这次有了妈妈的声音。
  6. "她不说什么,就问我今天吃饭了没。"
    • 音频设计"不说什么"之后留 3 秒静默(仅电话底噪),呼应 MSG-08 的 6 秒呼吸声,形成跨消息的听觉对位。
  7. "我现在打给你,你不接也没事。妈妈给你发个语音,你什么时候想听就听。你不用回我。"
  8. "你今天接了好多电话吧。妈妈不打扰你了。晚安。"

无选择交互。让玩家安静听完。播放结束 → 触发结局判定。

如果 MOM_LINK=falseMSG-16 不播放。MSG-15 结束后,静默 5 秒(环境底噪),然后直接进入结局判定。此时结局 A 的条件不可能满足(缺少 MOM_LINK),流程会落入结局 B/C/D。


5. 分支判定变量

变量 触发条件 影响
MOM_LINK 是否接妈妈两次电话中的至少一次 / 是否回复"睡了吗?" 决定 MSG-16 是否播放
HE_BACK 是否答应阿哲过来拿耳机(MSG-11 右滑) 决定 MSG-12 前是否插入门铃场景
GROUP_REPLY 是否在寝室群里回复过(MSG-07 非沉默) 决定彩蛋消息
ZHOUNAN_DEPTH 给周南的回应深度(累计) 决定结局独白 + self-3y 版本
ZHOUNAN_SHARE 是否在周南对话中选择"分享自己"MSG-13 上滑/右滑 R3 右滑) 决定隐藏结局
SELF_RECORD 是否听完 3 年前备忘录(MSG-15) 决定结局类型
PROFILE 全场行为聚合画像:avoidance / engagement / nostalgia 决定 self-3y 版本 + 环境音
UNREAD_COUNT 未播放/未接听的消息数(初始 16,每播放或接听 −1) 决定结局 C/D
REPLY_COUNT 林夏实际发出回应的消息条数 决定结局 C

REPLY_COUNT 计数规则

  • 选择右滑/左滑/上滑/单击(发出语音回应)→ +1
  • 下滑(沉默)→ 不计
  • 电话接听后自动回应(MSG-01/03/14)→ +1
  • 双击跳过整条消息 → 不计
  • 发表情(MSG-07 单击)→ +1

变量影响层级

并非所有选择都对结局产生等量影响。设计上区分核心变量氛围变量

层级 变量 影响 相关消息
核心 MOM_LINK 决定 MSG-16 是否播放(妈妈长独白),直接影响结局 A MSG-01, 10, 14
核心 HE_BACK 决定门铃场景是否触发,区分结局 A/B MSG-11
核心 ZHOUNAN_DEPTH + ZHOUNAN_SHARE 决定隐藏结局 E MSG-12, 13
辅助 SELF_RECORD 决定结局 A 是否可达 MSG-15
氛围 GROUP_REPLY 影响彩蛋消息,但不改变结局走向 MSG-07
氛围 PROFILEavoidance/engagement/nostalgia 影响环境音色调 + self-3y 版本,不直接决定结局 全局累计
统计 UNREAD_COUNT / REPLY_COUNT 决定结局 C/D 的触发条件 全局自动累计

设计原则:HR、安安、寝室群、小美醉酒等消息的选择主要影响 PROFILE 氛围和当下的对话反馈,让玩家"听得出这一句不一样"。真正改变结局走向的是妈妈线、阿哲线、周南线三条核心关系。玩家在这三条线上的选择会产生明确的、跨消息的后果。

PROFILE 计分细则

行为 avoidance engagement nostalgia
漏接电话(不动) +2 0 0
双击跳过消息 +2 0 0
单击回"嗯" +1 0 0
左滑冷淡回应 +1 0 0
下滑沉默 +2 0 +1
右滑温暖回应 0 +2 0
上滑追问 0 +2 0
Round 3 中选择"分享自己" 0 +1 +1

最终取分数最高项作为 PROFILE 标签。


6. 结局系统(5 种)

结局 A · "明天就明天吧"

触发MOM_LINK=true + SELF_RECORD=true + 至少回了周南或群里 1 条 优先级2

  • 妈妈语音播完。林夏起身,环境声:走到厨房、烧水、坐在小板凳上。
  • 林夏轻声自语:"明天再说。"
  • 烧水壶滴的一声 → 游戏结束。

结局 B · "AirPods 我寄给你"

触发HE_BACK=false + 回过周南至少 1 条 优先级3

  • 林夏给阿哲发语音"我寄给你,地址发我"(打字音效 → 发送音效 → 林夏语音播放)。
  • 林夏轻声自语:"寄出去了。"
  • 一个"清醒地走"的结局。

结局 C · "全部已读"

触发UNREAD_COUNT=0 且 REPLY_COUNT≤2 优先级4

  • 系统语音播报实际数值:"今晚共 16 条新消息,已读 {16UNREAD_COUNT},未回复 {16REPLY_COUNT}。"
  • 长沉默。林夏轻声自语:"今晚就这样。"

结局 D · "明天再开机"

触发UNREAD_COUNT≥8结局 E/A/B/C 均不满足(真正兜底) 优先级5(兜底结局)

  • 系统语音提示电量 1%。触感震动 + 白噪 → 彻底黑。
  • 第二天清晨鸟叫。林夏轻声自语:"早上好。"

结局 E · 隐藏 · "周南,我也记得你"

触发ZHOUNAN_DEPTH≥2 + ZHOUNAN_SHARE + PROFILE=engagement 或 nostalgia 优先级1(最高优先级)

  • 周南秒回:"那我下次出差来北京,请你吃饭好不好。"
  • 系统播放林夏的轻笑——这是全游戏唯一一次林夏主动笑出声。
  • 林夏轻声说:"好。"

结局判定规则

当玩家同时满足多个结局条件时,按优先级数字从小到大判定,取第一个满足的结局:

E(隐藏,优先级 1)→ A(优先级 2)→ B(优先级 3)→ C(优先级 4)→ D(兜底,优先级 5)
  • 结局 E 最稀有、最难触发,一旦满足则覆盖其他结局。
  • 结局 D 是真正的兜底结局——无论玩家怎么玩,只要 E/A/B/C 都不满足就一定进入 D。UNREAD_COUNT≥8 是 D 的常见触发场景,但不是唯一入口。
  • 结局 A 和 B 可能同时满足(例如 MOM_LINK=true + SELF_RECORD=true + HE_BACK=false + 回过周南),此时优先触发 A,因为 A 是更完整的情感闭合。

7. 音频设计

7.1 音频资产分类

类别 说明 数量 制作方案
林夏语音 主角对话回应(通用 + 专属) ~55 条, ~100s VoxCPM2 声音克隆
NPC Round 1 语音 各角色主要台词 ~30 条, ~300s VoxCPM2 声音克隆
NPC Round 2/3 反应 对话分支反应 ~54 条, ~205s VoxCPM2 声音克隆
self-3y 三版 时光胶囊备忘录 ×3 3 条, ~150s VoxCPM2linxia 音色)
UI 短音效 提示音、选择反馈、打字/发送 ~12 种 Web Audio 合成(无需文件)
BGM 4 段主题音乐 4 段 Suno / Udio 生成
环境音 卧室夜噪、酒吧、街声 4-5 段 Freesound (CC0/CC-BY)
角色铃声 来电铃声 3-4 段 简单合成或 Suno 短旋律

7.2 TTS 方案:VoxCPM2 本地声音克隆(唯一主链路)

注意:本项目 TTS 统一使用 VoxCPM2执行文档_音频素材制作.md 中早期记录的 CosyVoice2 方案作为备用,不作为主生产链路。批量脚本、音色目录、QA 标准均以 VoxCPM2 为准。

所有角色语音使用 VoxCPM2(本地部署,无需外部 API),通过声音克隆实现一致的角色音色。

服务启动

bash /home/xsl/AutoVideo/start-voxcpm.sh
# uvicorn 服务: http://127.0.0.1:8000

API 接口

  • POST /v1/voices — 注册参考音频(wav_base64)→ 返回 voice_id
  • POST /v1/speech — 合成语音(text + voice_id)→ 返回 WAV
  • 输出格式:48kHz 16-bit PCM WAV
  • 参数:cfg_value(默认 2.0)、inference_timesteps(默认 10

声音克隆流程

参考音频 (voice/{角色}/*.wav)
  → 转换为 16kHz mono WAV
  → POST /v1/voices 注册 → voice_id
  → POST /v1/speech 合成台词 → WAV
  → ffmpeg atempo 调速 → 最终音频

7.3 角色音色配置

角色 参考音频目录 语速 音色描述
林夏 voice/linxia/ 1.00 24 岁女生,疲惫但清醒,克制但有温度
妈妈 voice/mom/ 0.92 50 上下,温柔不黏腻,西部口音轻微
阿哲 voice/azhe/ 1.05 27 岁,城市男生,理性偏冷
小美 voice/xiaomei/ 1.10 24 岁,活泼,语速快
小美(醉) voice/xiaomei/ 0.95 同音色,降速模拟醉态
周南 voice/zhounan/ 0.95 26 岁,温和略拘谨
HR 王姐 voice/hr/ 1.00 38 岁,职场化,疲惫的善意
安安 voice/anan/ 1.15 23 岁,活泼,比小美更嗲
室友 A voice/dorm_a/ 1.10 年轻女声,兴奋
室友 B voice/dorm_b/ 1.00 年轻女声,随和
室友 C voice/dorm_c/ 0.95 年轻女声,温柔
外卖小哥 voice/delivery/ 1.20 30 岁,麻利
self-3y voice/linxia/ 1.15 3 年前的林夏,更明朗(同一音色,不同语速)

7.4 批量合成脚本

# 启动 VoxCPM2 服务
bash /home/xsl/AutoVideo/start-voxcpm.sh

# 使用 WAV 参考音色,批量合成所有台词
python audio/batch_tts_voxcpm.py --source voice

# 只合成特定角色
python audio/batch_tts_voxcpm.py --source voice --role linxia

# 干跑查看任务列表
python audio/batch_tts_voxcpm.py --source voice --dry-run

7.5 UI 音效(Web Audio 合成,无需文件)

所有 UI 音效通过 SFX_UI 对象在浏览器端实时合成:

音效 用途 实现方式
tap 单击确认 880Hz, 45ms
swipe 滑动确认 660Hz, 60ms
error 无效操作 200Hz square, 100ms
notify 新消息到达 523Hz + 659Hz 双音
choiceReady 进入选择 440→523→659 三音上行
choiceWarm 右滑温暖 440→659Hz 上行
choiceCold 左滑冷淡 523→349Hz 下行
choiceCurious 上滑追问 523→784Hz 快速上行
choiceSilent 下滑沉默 330Hz 极轻
typing 打字中 6 次随机频率短脉冲
send 发送 400→900Hz 上扫
longPress 长按确认 330→440Hz 缓升
ending 结局 三音渐弱

7.6 BGM 与环境音

曲目 用途 描述
M1 全程低氛围 Loop lo-fi midnight bedroom, soft piano, light vinyl noise
M2 MSG-08/09 悬疑 subtle ambient suspense, low drone
M3 MSG-13 周南段落 gentle indie folk, fingerpicked guitar
M4 MSG-16 结尾 warm cinematic piano, single melody line

环境音根据 PROFILE 累计分数动态调整:

  • engagement 倾向:柔和雨声、远处隐约的音乐
  • avoidance 倾向:安静、时钟滴答、远处车声
  • nostalgia 倾向:梦幻混响、远处风铃

8. 技术实现

8.1 技术栈

纯前端 Web 应用(单 HTML + JS),无需后端服务器:

  • 手势识别gestures.js — 7 种手势(单击、双击、长按、四方向滑动)
  • 音频管理audio.js — Web Audio API 合成 + HTML5 Audio 播放
  • 触觉反馈haptics.js — Vibration API(增强反馈,非必需;见下方降级说明)
  • 剧情数据story.js — 16 条消息的完整对话树
  • 游戏引擎engine.js — 状态机 + 异步对话流程
  • 行为画像profile.js — PROFILE 计分 + 状态变量

8.2 状态机

idle → notification/ringing → playing → choosing → responding → reacting → [choosing | idle] → ending
状态 说明 可用手势 进入音效
idle 等待消息 长按(播报状态)
notification 消息到达提示中 单击(播放)、双击(跳过)、长按(播报状态) 提示音 + 震动
ringing 来电铃声中 单击(接听)、左滑(拒接)、长按(播报来电人) 铃声 + 长震
playing NPC 语音播放中 双击(跳过语音)、长按(重听本句)
choosing 等待玩家选择 右滑、左滑、上滑、下滑、单击、长按(重听 NPC 上一句) 三音上行 + 长-短-长震动 + "等待回复"语音
responding 林夏语音播放中 双击(跳过) 打字音效
reacting NPC 反应播放中 双击(跳过)
ending 结局播放中 三指点击(退出) 结局专属音效

关键playing → choosing 的状态切换必须有明确的听觉分界(三音上行 + 震动模式变化 + "等待回复"语音提示),确保玩家在纯音频环境下不会误以为自己还在听消息。单击在 notification 状态是"播放",在 choosing 状态是"嗯"——两者之间有明确的进入选择态提示,不会混淆。

8.3 异步对话流程

// 进入选择态时的明确确认
function enterChoosing(choices) {
  state = 'choosing';
  SFX_UI.choiceReady();             // 三音上行
  haptics.pattern([200, 100, 80, 100, 200]);  // 长-短-长 震动
  speak('等待回复');                  // 系统语音确认
  currentChoices = choices;
}

// 长按处理:任何状态下可用
async _handleLongPress() {
  SFX_UI.longPress();
  if (state === 'choosing' || state === 'playing') {
    // 重播上一句 NPC 语音
    await playVoiceAsync([lastNpcAudio]);
  } else {
    // 播报当前状态
    speak(getStatusText());  // 如:"小美的消息,等待回复"
  }
}

async _handleChoice(dir) {
  const choice = currentMsg.choices[dir];

  // 1. 情感音效
  SFX_UI[emotionSfx[dir]]();

  // 2. 打字 → 发送
  state = 'responding';
  SFX_UI.typing();
  await sleep(800);
  SFX_UI.send();
  await sleep(300);

  // 3. 林夏的回应
  if (choice.linxia) {
    await playVoiceAsync([choice.linxia]);
  } else {
    await sleep(1500);  // 沉默
  }

  // 4. NPC 反应
  if (choice.npcReact) {
    state = 'reacting';
    await playVoiceAsync(choice.npcReact);
  }

  // 5. 后续对话(Round 3
  if (choice.followUp) {
    state = 'playing';
    await playVoiceAsync(choice.followUp.audio);
    if (choice.followUp.choices) {
      enterChoosing(choice.followUp.choices);
      return;
    }
  }

  // 6. 推进到下一条
  proceedToNext();
}

8.4 跳过机制

respondingreacting 状态中双击:

  • 设置 _skipRequested = true
  • 中断当前语音播放
  • 异步函数在每个步骤检查 skip 标志,快速跳到下一阶段

8.5 全局安全手势

除 §2.1 的 7 种剧情手势外,有两种全局安全手势,它们的优先级高于任何剧情手势,在所有状态下均可触发:

手势 功能 优先级
两指长按(1s 暂停游戏 最高——立即中断当前音频播放,进入暂停状态
三指点击 安全退出 最高——系统语音确认后跳转到支持资源页

实现要求

  • gestures.js 在手势识别时先判断触摸点数量:≥3 → 安全退出,=2 且持续 ≥1s → 暂停,=1 → 进入剧情手势判断。
  • 暂停状态下仅响应:单击(继续)、三指点击(退出)。
  • 安全手势不影响任何游戏变量(PROFILE、REPLY_COUNT 等不变)。

8.6 触觉降级策略

触觉反馈(Vibration API)是增强体验,不是必需通道。iPhone Safari 不支持 Vibration API,部分 Android 浏览器也可能受限。设计原则:

  • 所有状态切换必须有独立的音效/语音确认,不能仅靠震动区分。
  • haptics.js 在初始化时检测 navigator.vibrate 是否可用,不可用时静默降级。
  • 测试时必须在关闭震动的情况下完整通关一遍,确认纯音频也能正常游玩。

8.6 隐私承诺

本作不申请任何敏感权限

  • 不要麦克风、摄像头、位置、通讯录
  • 不需要联网(全程离线可玩)
  • 仅使用:触觉震动 + 触控输入 + 系统音量
  • 玩家行为数据只在本地存储,从不上传。

9. 音频资产清单

林夏语音

类别 数量 平均时长 总时长
通用短句(嗯、好、知道了等) ~10 条 1s ~10s
消息专属台词 ~45 条 2s ~90s
合计 ~55 条 ~100s

NPC 语音(Round 1 主台词 + Round 2/3 反应)

角色 Round 1 Round 2/3 总条数 总时长
妈妈 11 段 10 条 ~21 条 ~340s
小美 7 段 10 条 ~17 条 ~120s
阿哲 2 段 8 条 ~10 条 ~50s
周南 5 段 10 条 ~15 条 ~80s
HR 王姐 1 段 3 条 ~4 条 ~20s
安安 1 段 5 条 ~6 条 ~20s
寝室群 5 段 8 条 ~13 条 ~50s
外卖小哥 1 段 0 ~1 条 ~5s
self-3y 3 版 0 ~3 条 ~150s
合计 ~90 条 ~835s

总计

  • 林夏 ~55 条,~100 秒
  • NPC ~90 条,~835 秒
  • 总语音约 145 条,约 15.5 分钟
  • UI 音效:13 种,全部 Web Audio 合成
  • BGM4 段
  • 环境音:4-5 段

10. 项目文件结构

blind/
├── game/
│   ├── index.html          # 入口
│   └── js/
│       ├── main.js         # 启动 + 事件绑定
│       ├── gestures.js     # 7 种手势识别(含长按)
│       ├── haptics.js      # 触觉反馈
│       ├── audio.js        # 音频管理 + UI 合成音效
│       ├── story.js        # 16 条消息剧情数据
│       ├── engine.js       # 游戏引擎(状态机 + 对话流程)
│       ├── profile.js      # 行为画像 + 状态变量
│       └── utils.js        # 工具函数(sleep, dbg 等)
├── audio/
│   ├── mp3/
│   │   ├── linxia/         # 林夏语音(当前 edge-tts 版本)
│   │   └── react/          # NPC 反应语音(当前 edge-tts 版本)
│   ├── 00_raw/tts/         # VoxCPM2 生成原始 WAV
│   └── batch_tts_voxcpm.py # VoxCPM2 批量合成脚本
├── voice/                   # VoxCPM2 参考音色(WAV
│   ├── linxia/             # 林夏
│   ├── mom/                # 妈妈
│   ├── azhe/               # 阿哲
│   ├── xiaomei/            # 小美
│   ├── zhounan/            # 周南
│   ├── hr/                 # HR 王姐
│   ├── anan/               # 安安
│   ├── dorm_a/             # 室友 A
│   ├── dorm_b/             # 室友 B
│   ├── dorm_c/             # 室友 C
│   └── delivery/           # 外卖小哥
├── gdd/
│   └── redesign_v2.md      # v2 重构设计文档
└── 林夏.md                  # 本文档(总设计纲要)

11. 风险与对策

风险 对策
VoxCPM2 长情绪戏(妈妈 4 分钟)质感不够 分段录制 + 后期拼接,预留调参时间
声音克隆音色不够区分 每角色选择差异大的参考音频 + 调速区分
60+ 分支语音管理复杂 batch_tts_voxcpm.py 统一管理,命名规范
手势误判导致选错 滑动阈值 30px + 40°范围;双击间隔 250ms 内
玩家不理解手势含义 MSG-02(小美教学关)强引导,每步正向反馈
女性玩家代入感 编剧组需有 24 岁左右女性参与
题材敏感 见下方"情绪安全设计"专节

情绪安全设计

本作涉及分手、裁员、独居、深夜来电、家庭创伤回忆等高情绪浓度内容,必须认真对待玩家的心理安全。

开场内容提示(游戏启动后、第一条消息前):

  • 系统语音播报:"本游戏涉及情感压力、职场失落、家庭关系等主题。如果你当前情绪状态不佳,建议择日再玩。游戏过程中,两指长按可暂停,三指点击可直接退出。"

游戏中安全机制

  • 随时暂停:任何状态下两指同时长按 1 秒,游戏暂停,系统语音:"游戏已暂停。单击继续,三指点击退出。"(两指长按与单指双击完全不同,避免误触跳过)
  • 安全退出:三指同时点击,系统语音确认后直接跳转到结尾支持资源页。
  • 长按状态播报:让玩家随时确认自己在哪,减少焦虑感。

结尾支持资源(任何结局播放完毕后):

  • 系统语音:"感谢你陪林夏度过这一夜。如果你现在需要和人说说话:全国 24 小时心理援助热线 400-161-9995;北京心理危机研究与干预中心 010-8295-1332。"
  • 音频播放完毕后保持在屏幕上(即使是纯音频游戏,此处允许显示文字——用户此时可以看手机了,屏幕"修好了"是一个隐喻)。

叙事安全原则

  • 林夏的自救不完全寄托在妈妈或男性角色身上——妈妈提供的是"被记住"的感觉,周南提供的是"被看见"的感觉,但最终让林夏起身烧水、给阿哲寄耳机的,是她自己的选择。
  • MSG-16 妈妈语音中"那年夏天我没死"这句表述需谨慎处理:妈妈的语气是平静追忆而非戏剧化,TTS 合成时 emotion 设为 gentle, peaceful,避免制造突然的情绪冲击。

12. 制作流程

阶段 关键交付
剧本定稿 16 条全脚本 + 5 结局完整对白 + 所有分支台词
参考音色准备 voice/ 下 12 个角色参考音频确认
VoxCPM2 批量合成 ~145 条语音 + 语速调整 + 质检
BGM + 环境音 4 段 BGM + 环境采样
程序集成 story.js 对接最终音频路径 + 全流程调试
测试 全 5 结局通关测试 + 分支覆盖

附录 · 一句话给主创

"这不是一个让女孩们更难过的游戏。这是一个让她们在难过的时候,知道有人也曾这样难过、并且活下来的游戏。"