我制作了一款关于记忆的视觉小说游戏:《桥上没有回声》

我制作了一款关于记忆的视觉小说游戏:《桥上没有回声》
Nihil写在前面
很长一段时间里,我都想把一些过去发生的事情记录下来。它们没有明确的开始,也没有一个可以被概括成一句话的结局。留下来的,更多是一些散落在生活里的片段:一起完成过的作业、深夜仍然在线的游戏、从异地带回来的礼物、一把在雨里遗失的伞,以及一些写到一半又停下来的消息。这些事情已经过去,却没有真正从记忆里消失。它们会在某个夜晚、某张照片、某件旧物,或者某段聊天记录里重新出现。每次重新想起,我都会意识到,自己真正想记录的并不是“当时发生了什么”,而是这些事情为什么会被记住,以及它们如何在很长一段时间里影响一个人。最开始,我只想写一篇文章。后来我发现,单纯按照时间顺序把经历写下来,很容易变成一份只对自己有意义的私人记录。于是,我开始尝试另一种形式。我把现实经历拆开、筛选并重新组合,再用视觉小说的方式为它们建立场景、节奏和停顿。这个项目后来有了名字:
《桥上没有回声》。
为什么选择视觉小说
视觉小说是一种看起来很接近普通文字,实际表达方式又与文章明显不同的载体。文章通常由作者控制阅读节奏。视觉小说里,下一句话需要玩家主动点击,背景、音乐和人物也会随着文本逐渐出现。玩家会在黑屏、转场和场景停顿之间,获得一些不完全由文字提供的感受。这正好符合我对“记忆”的理解。
记忆很少完整地出现。它更像一些被触发的片段:某句话突然变得清晰,某个夜晚短暂重现,随后画面又回到现在。有些细节被记得很牢,有些部分已经模糊,甚至同一件事在多年以后也会出现不同的理解。如果只是把这些内容连续写成文字,它们会显得过于整齐。视觉小说允许我保留这种断续感。场景切换可以代表一次重新读取,黑屏可以代表记忆暂时中断,音乐的进入与退出也能帮助玩家感受到不同章节之间的距离。
游戏最终使用 Ren’Py 8.5.3 制作。Ren’Py 本身以文字叙事为核心,可以直接处理角色对白、菜单选择、章节跳转、存档、回看、多语言和打包。对我来说,它最大的优点不是能够实现多么复杂的系统,而是让我可以把大部分精力留给剧本、画面和整体节奏。
从一份剧本到一个可以运行的项目
真正开始制作以后,我很快发现,“写完剧本”和“完成一款游戏”是两件完全不同的事。剧本只需要考虑内容本身。游戏还要处理章节如何进入、人物何时出现、背景怎样切换、音乐从哪里开始、选择如何保存,以及玩家完成一个结局以后会留下什么状态。项目的主流程由 script.rpy 串联。游戏从序章开始,随后依次进入第一至第五章、终章和附章。各章节被拆分到单独的 .rpy 文件中,主流程通过 call 逐章调用。这样做以后,修改某一章时不需要在一个超长文件中来回寻找,也方便后续单独检查章节的文字和演出。
每章开头都会调用统一的 chapter_card,显示章节编号和标题。章节卡片不是一张提前制作好的图片,而是项目中单独实现的界面。它负责处理文字出现、短暂停留和淡出,让章节之间有一个相对一致的过渡。这也是我最早感受到“文章”和“游戏”区别的地方。在文章里,一个二级标题就足以区分段落;在游戏中,章节转换需要给玩家一个缓冲。如果前一段情绪刚刚结束,下一张背景立刻出现,阅读体验会显得非常急促。章节卡片的作用,实际上是在两个记忆片段之间留出一点空白。
重新组织故事,而不是照搬现实
剧本整理是整个制作过程中修改次数最多的部分。最初的文本更接近按时间顺序记录经历。这样的写法对于我自己很清楚,对玩家却不一定有效。很多现实中重要的事情,如果缺少前后铺垫,放进作品里只会变成一连串“作者认为很重要”的细节。
因此,我逐渐把重点从“事情发生的先后”转向“情绪如何发生变化”。例如,会议室通宵准备材料的内容,曾经在不同章节之间调整。最后它被放进第一章后半部分,因为这里更适合承接最初的普通相处,让关系从日常合作逐渐进入更长时间的陪伴。又例如,“红茶奶冻”这样很小的细节,被保留在第二章。它本身并不推动重大情节,却能让角色之间的相处显得具体。视觉小说中的关系不能只依靠旁白说明,玩家需要从这些普通的选择和习惯中,逐渐感受到两个人曾经共享过怎样的生活。
第四章的礼物则使用了单独的 CG。礼物在故事里并不是一个简单的纪念品。它承载了主人公后来反复理解的一段记忆,所以仅靠对白是不够的。把它作为画面单独展示,相当于让玩家和主人公一起停下来,重新看一眼这件已经被赋予额外意义的物品。
“回声终端”是怎样加入故事的
当章节逐渐变多以后,我遇到了一个结构问题:这些记忆为什么会在现在重新出现?
如果只是让主人公不断回想过去,故事很容易变成连续闪回,也缺少一个能够贯穿全篇的叙事接口。于是,我加入了“回声终端”。故事开始于多年以后。主人公收到一台来自母校的旧式终端。终端会逐步读取过去留下的痕迹,让那些已经沉入生活深处的片段重新出现在屏幕上。它一方面是故事中的设备,另一方面也是整个游戏的叙事界面。终端能够读取,却无法真正改变过去。它展示的内容也不是完全客观的录像,而是经过记忆筛选之后留下的部分。这样一来,章节之间的跳转、画面中断和片段缺失,都可以成为故事本身的一部分。贯穿全篇的另一个意象是“桥”。桥连接两端,也意味着经过、停留与离开。它连接过去与现在,连接靠近与疏远,也连接那些已经说出口的话和仍然留在输入框里的文字。
游戏叫《桥上没有回声》,并不是因为所有表达都注定得不到回应。有些声音确实被听见了,只是它们未必会以说话者期待的方式返回。人与人之间的理解,也很少表现为完全相同的情感。很多时候,能够认真表达,也能够接受彼此之间的差异,本身已经是一种抵达。
选择系统与三个结局
游戏中一共有五处主要选择,分布在序章、第二章、第三章、第五章和终章。前面的选择会记录主人公面对记忆时的倾向。例如,他是否更看重某段回忆,是否意识到关系中的边界,是否仍然希望从过去得到证明。这些倾向使用了诸如 memory_weight、boundary_awareness 一类的变量记录。最初,我确实考虑过使用累计分数决定结局。玩家在前面做出的每一次选择都会增加或减少不同数值,最终由系统判断进入哪一个结局。后来我放弃了这种方式。
原因在于,这三个结局并不适合被解释成玩家此前行为的奖惩。如果“边界感更高”自动进入某个结局,“表达欲更强”又自动进入另一个结局,系统就会暗示其中存在更正确的处理方式。这与作品最终想表达的内容并不一致。因此,现在的结局由终章最后一次选择直接决定。前面留下的变量仍然保存在项目中,它们记录了早期设计的痕迹,也可以影响局部文本,但不会替玩家决定最后该怎么做。
终章的最后,玩家可以选择:
- 把还没有说完的内容继续写下去并发送;
- 把信写完,保存在草稿中;
- 不从长信开始,只发送一句普通问候。
这三个选择分别进入三个独立结局文件。每个结局都会切换对应的背景音乐,写入一个 persistent.seen_* 持久化标记,然后显示结局标题。持久化标记不会随着普通存档结束而消失,因此项目能够记住玩家已经看过哪些结局,也为以后增加结局回顾或解锁界面留下了基础。
《回声抵达》
主人公把当下能够说清楚的想法完整表达出来。这不是为了要求对方给出某种答案,也不是为了证明过去必然意味着什么。表达的意义在于,他终于能够对自己的感受负责,并以不替对方下结论的方式,把它交到另一个人面前。
《回声归档》
主人公写完了那封信,却没有发送。那些话并没有因此失去意义。写作本身已经完成了一次整理,信件被保存下来,成为一份对当下心情的记录。有些表达不一定需要抵达另一个人,其实我们自己已经抵达了。
《回声渐轻》
主人公没有从一封长信开始,而是发送了一句普通的问候。故事从过去回到现在。重要的不是重新确认旧日关系,而是承认时间仍在继续,人与人仍然有可能以新的方式说话。三个结局不构成传统意义上的好结局、普通结局和坏结局。它们是面对重要记忆时,三种不同却都真实的处理方式。
人物、背景与演出
项目中的人物立绘主要包括男主、女主,以及身份尚未明确时使用的剪影版本。人物位置没有在每次出现时单独填写,而是通过 boy_pos、girl_pos 等 transform 统一控制缩放和站位。这样修改人物大小或屏幕位置时,只需要调整一处定义。人物出现和消失大多使用淡入淡出,而不是让两张立绘直接切换。这个细节看起来很小,实际会直接影响阅读感受。如果上一位角色还没有完全消失,下一位角色就立刻覆盖上来,画面会出现短暂重叠;如果每次切换都过快,人物对白又会显得像机械地轮流播放。背景和 CG 统一放在 program/game/images/ 中。大部分背景采用简约的动漫风格,场景集中在夜晚、雨天、清晨和灯光较暗的室内。这样的处理既符合回忆的氛围,也避免背景中出现过多会分散注意力的元素。
主菜单与章节界面
Ren’Py 自带一套可以直接使用的界面,不过默认界面很容易让作品保留明显的模板感。因此,我对主菜单进行了单独调整。主菜单使用 images/bg_cover.png 作为封面背景,并重新安排了游戏标题、版本号和导航按钮的位置。我的目标并不是设计一个功能非常复杂的菜单,而是让玩家在进入游戏前,就能感受到作品的整体色调。章节卡片则负责统一章节之间的节奏。除此之外,设置页面还加入了语言切换入口。第一次启动游戏时,玩家会先看到语言选择框;进入主菜单以后,也可以重新切换语言。这些界面工作占用的代码不一定很多,却需要不断测试。文字位置在中文界面中合适,切换成英文后可能变长;日文使用不同字体以后,字号和行距也可能发生变化。某个按钮在电脑上看起来居中,换一个分辨率后也可能出现偏移。这类问题通常不会让游戏完全无法运行,却会明显影响完成度。
音乐系统从 WAV 到 OGG
游戏采用“每章一首背景音乐”的方式组织声音。序章、各章节、三个结局和附章分别绑定自己的音乐。进入章节时播放对应 BGM,章节结束或结局完成后再统一停止。最初项目中曾经使用 WAV 文件。WAV 保留完整音频数据,文件体积较大。在实际测试时,部分播放方式没有按预期工作。后来我将正式使用的音乐统一转换为 OGG,并在代码中重新整理了引用。OGG 更适合游戏发布包,文件体积明显更小,也能够被 Ren’Py 稳定读取。项目中还保留了一部分旧音频定义。这些内容记录了早期方案,正式剧情已经统一使用新的章节 OGG 配置。它也提醒我,在项目进入发布阶段以后,需要继续清理旧引用和未使用资源,避免将不必要的文件一起打包。音乐播放时设置了循环和淡入效果。我也多次调整默认音量。背景音乐过大时,玩家会更注意旋律,而不是正在阅读的文字;音量太低,又会让场景显得缺少支撑。最终的目标不是让音乐单独被注意到,而是让它自然地留在文字之后。在这里感谢黑耀耀_officialてす提供的音乐资源!!!
中文、英文与日文
《桥上没有回声》支持简体中文、英文和日文。中文是原始文本,英文和日文翻译分别放在 program/game/tl/english/ 与 program/game/tl/japanese/ 中。Ren’Py 会按照相同文本标识读取不同语言内容,因此剧情结构不需要为每种语言复制一遍。真正麻烦的部分并不只是翻译对白。主菜单、设置、About 页面、章节标题、结局名称和系统提示也需要处理。不同语言的字符宽度不同,原本适合中文的按钮尺寸,换成英文后可能不够;日文字体和中文字体也不一定适合混用。
为此,项目增加了 multilingual.rpy,在切换语言时同步切换字体和相关界面文字。第一次启动游戏时会出现语言选择框。玩家完成选择以后,语言状态会被保存;之后仍然可以在主菜单或设置页面重新切换。多语言开发中还遇到过翻译冲突。Ren’Py 的翻译条目依赖稳定的标识。当原文被修改、重复生成翻译,或者两个文件中出现相同条目时,项目可能直接停止编译。错误日志中也曾记录过文本占位符写错导致的 NameError。
这些问题让我意识到,多语言不适合等到游戏全部完成以后再一次性加入。只要原文仍在频繁调整,翻译文件就会不断受到影响。后续更新时,也必须同步检查三种语言,而不能只确认中文能够运行。
测试中遇到的实际问题
自己能够完整通关,并不代表游戏已经可以交给别人。开发过程中,我对很多操作已经形成习惯。哪些位置需要停顿、哪里可以快速点击、某个按钮在什么地方,我都非常熟悉。第一次让其他人试玩以后,一些本地不明显的问题才真正出现。其中一个问题是连续跳页。我的朋友在推进剧情时,偶尔会出现一次点击跳过两页文本的情况。我自己的电脑上很难复现,因此需要同时检查鼠标双击、Ren’Py 的跳过设置、输入设备差异,以及界面中是否存在重复接收点击的区域。另外还有字体、音频路径、章节转场和打包图标等问题。开发环境中能显示的字体,打包后未必被正确包含;代码中引用的 WAV 已经改成 OGG,旧路径如果没有完全清理,就可能在其他电脑上报错;项目窗口使用了新图标,最终生成的可执行文件却可能仍然保留默认图标。
开发过程
整理剧本
从个人经历中筛选片段,确定序章、章节与终章的基本结构。
建立项目
使用 Ren’Py 建立角色、背景、音乐和章节调用关系,将长剧本拆分为独立文件。
调整叙事
加入“回声终端”和桥的意象,重新安排会议室、游戏、礼物与雨伞等片段的位置。
加入分支
在不同章节加入菜单选择和状态变量,最初尝试累计变量判定结局。
重做结局
取消分数自动判定,由终章最后一次选择直接进入三个并列结局。
完善演出
定制主菜单、章节卡片、人物站位、背景切换和每章 BGM。
多语言
加入英文、日文翻译,处理字体切换、语言选择和翻译冲突。
测试与发布
修复文本、音频和界面问题,制作安装包并准备 itch.io 页面。
打包与 itch.io 发布
当游戏能够从头运行到结尾以后,项目才真正进入发布准备阶段。Ren’Py 可以直接生成 Windows、Linux 和 macOS 分发包。这个项目主要面向 Windows 玩家,因此除了 Ren’Py 构建包之外,我还准备了 Inno Setup 安装脚本 no_echo_on_the_bridge.iss。与此同时,itch.io 页面也需要单独准备:
- 英文游戏简介;
- 游戏封面和截图;
- 支持语言;
- 预计游玩时间;
- 下载文件;
- 版本更新说明。
游戏本体与商店页面是两种不同的表达。游戏默认语言是中文,而 itch.io 页面主要面向更广泛的玩家,因此页面使用英文介绍,并明确说明游戏支持中文、英文和日文。第一次进入游戏时的语言选择,也可以减少玩家下载后面对中文主界面的困惑。发布包最终选择 ZIP,而不是只上传一个独立的 setup.exe。ZIP 更符合 itch.io 的常见分发方式,玩家可以直接解压运行,也方便以后通过 butler 或重新上传压缩包进行版本更新。安装程序仍然可以作为额外下载项保留,但不适合作为唯一文件。
关于真实与虚构
这正是我希望视觉小说能够做到的事情。
展开查看与作品有关的现实照片
这款游戏以个人记忆为创作基础,却不等同于现实记录。为了保护现实中的人与关系,也为了让故事能够独立成立,我对人物、场景、事件顺序和表达方式进行了重新设计。游戏中的角色首先是作品中的角色,其次才与某些真实经历存在联系。我也逐渐意识到,当一段私人经历被写成作品后,它就不再只属于作者。玩家会带着自己的经历进入故事。他们可能想起某位旧友、某段没有结果的关系,或者某个曾经被认真保存的普通物品。那一刻,作品中的桥、雨和礼物,也会拥有与我最初设想不同的意义。
最后
完成这款游戏以后,我并没有觉得过去因此获得了一个明确结局。更准确地说,它让我能够以一种新的方式看待那些记忆。有些事情曾经非常重要,今天依然值得珍惜;有些关系已经改变,也不代表过去因此失去意义。表达并不必然换来相同的回音,认真表达仍然可以成为一个人理解自己的方式。从技术上看,《桥上没有回声》并不是一个复杂的项目。它主要由文字、选择、图片、音乐和几个界面组成。然而正因为结构简单,每一处细节都会变得明显。一句话出现得太早或太晚,一张背景是否适合当时的情绪,一个结局是否在暗示玩家受到奖励或惩罚,这些问题都需要重新考虑。制作过程最终也不只是把剧本装进 Ren’Py,而是不断决定什么应该出现,什么应该被删除,什么应该交给玩家自己理解。
我们无法从时间里取回已经过去的部分。
真正能够拥有的,始终是现在。
愿每一个读到这里的人,都能温柔地记住那些曾经照亮过自己生活的相遇,也能在记忆之外,继续珍惜仍在发生的一切。







