文档整理: - 重写 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
186 lines
8.0 KiB
JavaScript
186 lines
8.0 KiB
JavaScript
// games/yinian.js — 《一念》
|
|
// 内心法庭。七个声音,一个决定。
|
|
// 单击=接纳这个声音,双击=让它走。
|
|
// 结局由接纳了哪些声音决定。
|
|
|
|
const GAME_YINIAN = {
|
|
id: 'yinian',
|
|
intro: '你坐在黑暗里,独自一人。今晚你必须做一个决定——你自己知道是什么。七个声音在等着和你说话。单击,让它说完。双击,让它走。',
|
|
first: 'v01',
|
|
bgm: null,
|
|
ambience: '../audio/mp3/ambience/amb_apartment_night_01_v1.mp3',
|
|
initialState: {
|
|
accepted: [], // 接纳的声音
|
|
rejected: [], // 放走的声音
|
|
// 关键标记
|
|
kept_fear: false,
|
|
kept_longing: false,
|
|
kept_apology: false,
|
|
kept_past: false,
|
|
},
|
|
|
|
scenes: {
|
|
// ── 第一个声音:三年前的自己 ──
|
|
v01: {
|
|
delay: 1000,
|
|
narrate: '第一个声音来了。是三年前的你。那时候你刚做了一个选择,你觉得那是对的。那时候的你说:我们当时想要的,你还记得吗?',
|
|
tap: {
|
|
label: '听它说完',
|
|
narrate: '你让三年前的自己说完了。她说,你当时很想要那件事。你知道那是真的。',
|
|
state: s => ({ accepted: [...s.accepted, '三年前的自己'], rejected: s.rejected, kept_past: true }),
|
|
next: 'v02',
|
|
},
|
|
doubleTap: {
|
|
label: '让它走',
|
|
narrate: '三年前的声音淡去了。你选择了不看它。',
|
|
state: s => ({ accepted: s.accepted, rejected: [...s.rejected, '三年前的自己'], kept_past: false }),
|
|
next: 'v02',
|
|
},
|
|
},
|
|
|
|
// ── 第二个声音:一个老朋友的留言 ──
|
|
v02: {
|
|
delay: 1000,
|
|
narrate: '第二个声音。是一个你很久没联系的朋友,她留了条语音。她说:你最近怎么样,我有点担心你。很久了,你一直没回。',
|
|
tap: {
|
|
label: '听下去',
|
|
narrate: '你把这份担心收进来了。她还在关心你,这件事是真实的。',
|
|
state: s => ({ accepted: [...s.accepted, '朋友的担心'], rejected: s.rejected }),
|
|
next: 'v03',
|
|
},
|
|
doubleTap: {
|
|
label: '让它走',
|
|
narrate: '留言消失了。你没有听完。',
|
|
state: s => ({ accepted: s.accepted, rejected: [...s.rejected, '朋友的担心'] }),
|
|
next: 'v03',
|
|
},
|
|
},
|
|
|
|
// ── 第三个声音:关于钱的恐惧 ──
|
|
v03: {
|
|
delay: 1000,
|
|
narrate: '第三个声音。是关于钱的恐惧。它说:你知道你现在能撑多久吗?如果这件事不成,你怎么办?它问得很具体,很冷静。',
|
|
tap: {
|
|
label: '正视它',
|
|
narrate: '你承认了:这份恐惧是真实的。你带着它,而不是假装它不存在。',
|
|
state: s => ({ accepted: [...s.accepted, '对钱的恐惧'], rejected: s.rejected, kept_fear: true }),
|
|
next: 'v04',
|
|
},
|
|
doubleTap: {
|
|
label: '先不管它',
|
|
narrate: '恐惧的声音淡下去了。你知道它还在,但今晚先不看。',
|
|
state: s => ({ accepted: s.accepted, rejected: [...s.rejected, '对钱的恐惧'], kept_fear: false }),
|
|
next: 'v04',
|
|
},
|
|
},
|
|
|
|
// ── 第四个声音:关于孤独的渴望 ──
|
|
v04: {
|
|
delay: 1000,
|
|
narrate: '第四个声音。是一种渴望——想要被人理解,想要有人在身边,想要不那么孤独。它来得轻,但是很深。',
|
|
tap: {
|
|
label: '承认它',
|
|
narrate: '你承认了这份渴望。它是真实的,不是软弱。',
|
|
state: s => ({ accepted: [...s.accepted, '对连接的渴望'], rejected: s.rejected, kept_longing: true }),
|
|
next: 'v05',
|
|
},
|
|
doubleTap: {
|
|
label: '让它走',
|
|
narrate: '渴望漂走了。你告诉自己,一个人也没什么。',
|
|
state: s => ({ accepted: s.accepted, rejected: [...s.rejected, '对连接的渴望'], kept_longing: false }),
|
|
next: 'v05',
|
|
},
|
|
},
|
|
|
|
// ── 第五个声音:一个未说出口的道歉 ──
|
|
v05: {
|
|
delay: 1000,
|
|
narrate: '第五个声音。是一个你一直没有说出口的道歉。你知道是对谁的,你知道说的是什么。这个声音只是在等你。',
|
|
tap: {
|
|
label: '说出来',
|
|
narrate: '你在黑暗里,说了那句对不起。没有人听见,但你说了。有什么东西松开了。',
|
|
state: s => ({ accepted: [...s.accepted, '那声道歉'], rejected: s.rejected, kept_apology: true }),
|
|
next: 'v06',
|
|
},
|
|
doubleTap: {
|
|
label: '还没准备好',
|
|
narrate: '道歉的声音还在那里。你没有说。它等着。',
|
|
state: s => ({ accepted: s.accepted, rejected: [...s.rejected, '那声道歉'], kept_apology: false }),
|
|
next: 'v06',
|
|
},
|
|
},
|
|
|
|
// ── 第六个声音:关于那个决定 ──
|
|
v06: {
|
|
delay: 1000,
|
|
narrate: '第六个声音。是今晚那个决定本身。它把两种可能都摆在你面前,不说话,只是等着你看清楚。',
|
|
tap: {
|
|
label: '看清楚它',
|
|
narrate: '你看了。两条路都有代价,你知道了。这不让选择变得更容易,但让它变得更真实。',
|
|
state: s => ({ accepted: [...s.accepted, '那个决定本身'], rejected: s.rejected }),
|
|
next: 'v07',
|
|
},
|
|
doubleTap: {
|
|
label: '还不想看',
|
|
narrate: '你把眼睛闭上了。还没准备好看那两条路。',
|
|
state: s => ({ accepted: s.accepted, rejected: [...s.rejected, '那个决定本身'] }),
|
|
next: 'v07',
|
|
},
|
|
},
|
|
|
|
// ── 第七个声音:现在的自己 ──
|
|
v07: {
|
|
delay: 2000,
|
|
narrate: s => {
|
|
const n = s.accepted.length;
|
|
if (n >= 5)
|
|
return '第七个声音,是现在的你。她说:你今晚接住了很多,你没有回避它们。这需要力气。';
|
|
if (n <= 1)
|
|
return '第七个声音,是现在的你。她沉默了很久,然后说:你把它们都推走了。也许你需要的不是答案,是喘息。';
|
|
if (s.kept_fear && !s.kept_longing)
|
|
return '第七个声音,是现在的你。她说:你把恐惧留下了,但把渴望推走了。你知道这意味着什么吗?';
|
|
return '第七个声音,是现在的你。她说:你接住了一些,放走了一些。这就是你今晚的状态。';
|
|
},
|
|
tap: {
|
|
label: '听下去',
|
|
narrate: '你听完了现在的自己说话。她说:不管你今晚做什么决定,你都已经在这里待过了。这算数。',
|
|
state: s => ({ accepted: [...s.accepted, '现在的自己'], rejected: s.rejected }),
|
|
next: null,
|
|
},
|
|
doubleTap: {
|
|
label: '就这样',
|
|
narrate: '你没有听完。你觉得你已经知道了你需要知道的。',
|
|
state: s => ({ accepted: s.accepted, rejected: [...s.rejected, '现在的自己'] }),
|
|
next: null,
|
|
},
|
|
},
|
|
},
|
|
|
|
getEnding: s => {
|
|
if (s.kept_apology) return 'reconcile';
|
|
const n = s.accepted.length;
|
|
if (n >= 5) return 'accept';
|
|
if (n <= 1) return 'release';
|
|
if (s.kept_fear && !s.kept_longing) return 'stuck';
|
|
return 'between';
|
|
},
|
|
|
|
endings: {
|
|
accept: {
|
|
narrate: '《接纳》。你今晚接住了很多,你没有选择只是轻装。也许你的决定,就是带着这些往前走。',
|
|
},
|
|
release: {
|
|
narrate: '《出发》。你今晚把大部分都放走了。你想要的也许是轻。轻装,也是一种勇气。',
|
|
},
|
|
stuck: {
|
|
narrate: '《困住》。你接受了恐惧,但推走了渴望。你知道风险,但忘了你想要的是什么。这个决定,也许需要再等一等。',
|
|
},
|
|
reconcile: {
|
|
narrate: '《和解》。你说出了那声道歉。不管今晚的决定是什么,你和自己和解了一点点。这很重要。',
|
|
},
|
|
between: {
|
|
narrate: '《悬在中间》。你接住了一些,放走了一些。你还悬在两个答案之间。也许这就是你今晚真实的状态。',
|
|
},
|
|
},
|
|
};
|