2026年9月18日星期五

在一间黑屋子里醒来:一个游戏故事可以怎么讲

前几天翻到 A Dark Room 的源码仓库,顺手 clone 下来读了一遍。八千行 JavaScript,没有框架,没有构建,连测试都没有。

但我读完之后坐在那儿愣了一会儿。让我惊叹的不是剧情有多曲折——它的剧情用三句话就能说完——而是它讲故事的方式。这些年玩过的游戏里,真正让我停下来想”这怎么做到的”的,没几个。

下面会把整个故事复述一遍。不想被剧透的,到此为止。

它做对了什么

从一个按钮开始

游戏打开的时候,屏幕上什么都没有。没有菜单,没有主界面,没有教程,没有”新游戏/读取/设置”。

只有一行字:生火

我现在觉得这是整个游戏最重要的一个设计决定。它把”给玩家看什么”这件事压缩到了极限——不是”尽量少”,是真的只有一个。你不需要理解任何东西,你只能做一件事,所以你做了。

对比一下现在大部分游戏的开场:一段过场动画交代世界观,一个引导 NPC 告诉你你是谁,一排还是灰色的技能树暗示你以后能变多强。全都在拼命告诉你”这个世界很大”。

A Dark Room 反过来:它先让世界小到只剩一个按钮,然后让它自己长大。

界面自己会讲故事

点下”生火”之后,页面标题变了。从「一间黑屋子」变成「一间有火光的屋子」。

游戏的名字就是它的初始状态。而你做的第一件事,就把这个名字改掉了。

后来解锁了外面,那个页签叫「一片寂静的森林」。随着人搬进来,它一路变成:一间孤零零的小屋、一个小村子、一个不小的村子、一个很大的村子、一个喧闹的村庄。

从头到尾,没有任何一句话告诉我”你的村子变大了”。我是看着那行字自己变的。

同理还有那些按钮。屋子里一开始只有”生火”,后来多出”添柴”,多出建造栏,多出制作栏,多出交易栏,多出武器栏。它们不是先隐藏再显示,是真的一个一个被创建出来的。

代码层面它就是这么做的——所有场景的 DOM 摆在一条横轴上,解锁一样东西就往里 append 一个节点。那个标志性的横向滑动切场景的效果,不是转场动画,它就是布局本身。

所以”这个世界在你手里一点点长出来”这个体验,不是特效,是架构直接产生的。

这一条我觉得最值得偷:能让界面承担的叙事,就别写成文字。

让玩家先做,再告诉他做了什么

那个冻僵的女人被你烤火救活,站起来说她会造东西。然后游戏做了一件很轻的事:给她加了一条产出,每十秒 +2 木头。

一个快冻死的人,变成了一个数字。

后面整个村庄都是这个模式。人不断搬进来——“一支车队踉跄着开进来,满载着忧虑,也满载着希望”——然后我打开面板,把他们一个个拖进工种:猎人、制革匠、熏肉匠、铁矿工、炼钢工。

村子会着火,屋里的人会死光。会闹瘟疫。我每次看一眼,确认产出没掉太多,接着点。

游戏从来没有说过一句”你在剥削他们”。它只是让我亲手把人变成了产出条目,做了几百遍。

最狠的是抓到小偷那一段。两个按钮:吊死他,放他走。

吊死他,被偷的东西一件不少还回来。放他走,他谢你,教你一点技巧。

没有评论,没有道德条,没有后续惩罚。它只是让”对的那个选择”在账面上亏一点。

**玩家做的动作本身就是论点,文本只负责在最后给这些动作命名。**这是游戏这个媒介独有的能力,但用得好的少见——大部分游戏的做法是让 NPC 站出来发表一段演讲,告诉你战争不好。

真相藏在形容词里

这是我最服的一点。

A Dark Room 的反转——你不是幸存者,你是殖民者——没有任何一个揭示场景。没有过场动画,没有 NPC 突然摊牌,没有一段藏在角落里的日志把一切说清楚。

它是几个词,出现在了不该出现的位置。

地图最边缘有一艘坠毁的飞船,是通关的钥匙。走到跟前,游戏说:

一艘流浪者飞船那熟悉的曲线,从尘土和灰烬里浮现出来。
幸好这些土著不会操作里面的机械。

两个词。

「熟悉的」——一艘外星飞船的轮廓,为什么对我来说是熟悉的。

「土著」——那些我收留、分配、指挥、保护的村民,在叙述者的词汇表里,叫土著。

而叙述者一直是我。

还有一处,拿到一台外星制造机的时候,提示是:「流浪者机械重新运转起来的那种熟悉的嗡鸣。终于有真家伙了。」

「终于」。在这之前我一直在用骨头和石头凑合。

还有我仓库里一路小心囤着的东西,名字叫「外星合金」。对谁而言是外星的?对村民。对我不是。

这些词全都出现在纯功能性的句子里。你完全可以读过去不当回事——我第一遍就是这么读过去的。但一旦对上了一个,剩下的全都跟着炸开。

**把真相放在形容词里,不要放在情节里。**情节是作者告诉你的,形容词是你自己发现的。发现的东西才留得住。

铺垫多长,反转就有多重

前三十分钟是纯粹的放置游戏多巴胺。数字涨,按钮解锁,页签变多。我完全没在想剧情,我在想下一个建筑还差多少木头。

等我终于停下来读那句「幸好这些土著不会操作里面的机械」的时候,我已经为这台机器投了两个小时进去。

反转的力量不来自那句话,来自我自己的沉没成本。

这是很多游戏搞反的地方。它们把反转当成一个信息——只要信息够劲爆就够了,所以急着抖出来。但反转的重量其实取决于玩家在反转之前投入了多少、认同了多少、自我说服了多少。

A Dark Room 用两个小时让你确信自己是个好人:你救了濒死的陌生人,你收留了流浪的家庭,你保护村子不被野兽袭击,你为他们打下了矿井。

然后它用一个形容词把这两个小时全部重新解释了一遍。

最重要的那段,允许你错过

地图上有一片沼泽。要用村民手工做的护身符才能触发,而那玩意儿在商店里能卖钱,绝大多数人会直接卖掉。

沼泽深处一间长满苔藓的木屋,坐着一个入定的老流浪者。他说:

他曾经率领庞大的舰队,开往一个又一个新世界。
为了喂饱流浪者的饥渴,带来无可估量的毁灭。
他如今待在这里,是在赎罪。

这是全游戏把背景说得最白的一段。而它是可选的,隐藏的,要花钱的,很多人玩通关也没见过。

正常的产品思路是:这么重要的信息必须保证每个玩家都看到,得放进主线。

但 A Dark Room 的判断是:**玩家自己拼出来的结论,比被告知的结论牢十倍。**所以它宁可让一部分人永远拼不出来,也不肯直接说。

我觉得这是对的。那些被塞进必经之路的”重要设定”,玩家读的时候是在被动接收;而我为了一个护身符绕路进沼泽读到这段,那种感觉完全不一样。

连存档机制都算叙事

通关之后,存档被删了。

然后下一局开始,地图上多了一个上一局没有的地标:一座被毁的村庄

一座被毁的村庄躺在尘土里。焦黑的尸体铺满地面。

空气里悬着流浪者飞船尾焰的金属味。

走进去,废墟中央的棚屋里还有补给。拿了:

前一代人的全部劳作都在这里。正等着被摘走。

翻源码确认过,这个地标只在有上一局存档时才会被注册进地图,而且捡到的每一样东西,都是上一局自己攒下来的。

也就是说:上一次我起飞的时候,用引擎的尾焰把那个村子烧掉了。

那些焦尸是救过我、养过我、给我挖矿炼钢、给我做那个护身符的人。而我这一局,正在做一模一样的事。

新周目继承是个纯系统性的设计,本来和剧情无关——大多数游戏就是”带点资源进下一周目,鼓励你重玩”。A Dark Room 把它变成了整个主题的最后一块拼图。

**存档、重玩、分数、成就,这些通常被当成”游戏系统”的东西,也是叙事资源。**而且因为玩家从不设防,它们打人更疼。

越努力,结局越惨

大部分游戏的结构是:做得越好,结局越好。支线做满,好感度刷够,解锁真结局,大家都活下来。

A Dark Room 反着来。

普通结局:你什么都不知道地飞走了,算是逃出生天。

而如果你走到地图另一个角落,打穿那艘坠毁的战舰,杀掉最后那个东西,拿到舰队信标再起飞,通关动画后面会多出四行字:

信标亮起一片纯蓝,然后暗了下去。飞船慢了下来。
渐渐地,庞大的流浪者母舰队出现在视野里。
巨大的世界舰在残骸云里不自然地漂着,伤痕累累,全都死了。

空气快没了。

座舱很冷。

然后整个屏幕上只剩下一个按钮。只有一个:

你毁掉一整颗星球,烧掉一个村子,杀穿一艘战舰,全都是为了回家。家早就没了。而你能做的最后一件事,是等。

探索的奖励是失去希望。

这需要很大的胆子。它等于告诉玩家:你在我这儿越投入,我给你的东西越难受。但正因为这样,那个”等”字才有分量——它不是叙事上的一个转折,它是对你这几个小时的总结。

它也有讲砸的地方

不吹成完美。

**中后期密度掉得厉害。**开荒地图那段文本量爆炸,但有效叙事被稀释了。大量地标就是纯战利品,没有任何剧情,翻开就是一个列表。前四幕那种”每一句话都在推进”的密度撑不住那么大的地图。

**战舰那条线和主线不是一个语气。**它是资料片移植过来的,单独一个文件,比主线所有事件加起来还长。动作场面多,留白少,写得挺好,但你能明显感觉到执笔的人和前面不是同一种呼吸。前面是”火在燃烧”,后面是”一个不稳定的自动机嗡嗡启动”。

**反转之后有点后继乏力。**真相对上之后,剩下的流程基本就是攒资源修船,故事没有再往前走一步。它把全部的力气压在了一个点上,这个点炸得非常响,但后面接不住。

第三条我觉得是最难避免的——单点反转型的故事都有这个问题。要解决就得让反转之后玩家的行为发生变化,但那意味着重做一套系统。

完整的故事

下面是从头到尾的复述。

你醒了。头疼,眼前模糊。屋子是黑的,很冷。

屏幕上只有一句话:生火。

你点了。火起来了。

火会灭,木头会烧完。你出门捡柴,回来添火,再出门捡柴。左边的通知一行一行浮上来又淡掉。

风在外面呼啸。
木头快没了。

然后某个晚上,门被撞开了。

一个衣衫褴褛的陌生人跌进门来,倒在角落里。

她在发抖。你往火里加柴。

陌生人打着哆嗦,小声嘟囔着什么。听不清她在说什么。

你继续加柴。

角落里的陌生人不抖了。她的呼吸平缓下来。

再过一会儿,她站起来了。

陌生人站在火边。她说她能帮忙。说她会造东西。

从这一刻起,你的木头每十秒 +2。

她给你搭了捕兽夹,夹子里有毛皮和肉。有东西可以换,就开始有人来。

夜里来了个陌生人。
一户风尘仆仆的人家住进了其中一间小屋。
一支车队踉跄着开进来,满载着忧虑,也满载着希望。

森林变成小屋,小屋变成村子,村子越来越大。

你打开面板,把他们一个个拖进工种里。那支满怀希望的车队,变成了每十秒结算一次的数字。

村子会出事。小屋会烧掉,屋里的人全死。会闹瘟疫。捕兽夹会被撕烂。你每次看一眼,确认产出没掉太多,接着点。

有一天村民从仓库里拖出一个人。

村民们从储藏室里拖出一个满身污垢的男人。
说他那伙人一直在偷补给。
说应该把他吊起来,杀一儆百。

吊死他,还是放他走。

后来有个游商卖给你一个旧指南针,地图就打开了。

六十一乘六十一的字符网格,村子在正中央,是唯一的一个 A。往外是森林、草原、荒原、废墟、矿井、洞穴、城镇。越远越要命。

带上熏肉和水出门,走几步吃一口,走几步喝一口,吃完就得回头,不然死在路上。

然后开始杀东西。一开始是野兽,后来是人。

一个瘦削的男人走过来,眼神疯狂。
一个拾荒者凑上来,想捡个便宜。

再后来是有组织的军队。铁矿煤矿硫磺矿全被军队占着,一个个打下来。

军队已被清除。矿井现在对工人是安全的了。

你闯空屋子,闯废弃的城镇,闯倒塌的城市,撬柜子翻尸体。在某座城市里你放了一把火:

那个小定居点显然已经烧了一阵子了。
住在这里的流浪者的尸体还在火里看得见。
还来得及抢救一点补给。

地图外圈有一些巨大的坑。

一个巨大的洞被深深切进地里,是上一次收割留下的证据。
他们拿走了想要的东西,然后走了。

地图最边缘有一艘坠毁的飞船。

一艘流浪者飞船那熟悉的曲线,从尘土和灰烬里浮现出来。
幸好这些土著不会操作里面的机械。

沼泽深处有个入定的老流浪者。

他说他曾经率领庞大的舰队,开往一个又一个新世界。
为了喂饱流浪者的饥渴,带来无可估量的毁灭。
他如今待在这里,是在赎罪。

而你仓库里一路囤着的东西,名字叫「外星合金」。

于是全部对上了。

流浪者是一个跑遍星海的掠夺文明。舰队开到一颗星球,抽干,离开,换下一颗。这颗星球已经被收割过一次了,那些巨坑就是上次留下的。

你是一个坠机后失忆的流浪者。醒在一间黑屋子里,救活一个土著女人,靠她起家,把整个土著社会改造成一台采掘机器,屠光一切挡路的原住民,只为了凑够零件修好一艘船飞走。

黑屋子里那个冻得发抖、需要被救的可怜人,从头到尾都不是你。

船修好,加固船体,升级引擎。起飞前游戏只问一句:

该离开这个地方了。不会再回来了。

起飞。躲过小行星带。冲出大气层。

通关。存档删除。

但没有全删。

下一局,又醒在一间黑屋子里。又是火,又是木头,又是那个跌进门来的陌生人。

只是地图上多了一座被毁的村庄。

一座被毁的村庄躺在尘土里。焦黑的尸体铺满地面。

空气里悬着流浪者飞船尾焰的金属味。

废墟中央的棚屋里还有补给。拿了。

前一代人的全部劳作都在这里。正等着被摘走。

你捡到的每一样东西,都是上一局你自己攒下来的。

地图另一个角落有一艘垂直插进地里的巨舰。

里面是一整座竖着的死城。船壳、工程舱、医疗舱、训练场、指挥甲板,一层层往上。

人类的军队在里面扎了营,墙上有粗糙的切割痕迹,地上散落着铜线碎屑——土著在笨拙地拆解你的科技,换几根铜丝。

从实验室跑出来的变异生物,收容舱全开着。

还有一直在待机的自动防御:

自动守卫仍在走廊里巡逻,浑然不知它们的主人早已离开。

船是怎么毁的,没人说。但一层层走上去,会发现路障的朝向不对:

这里没有发生过战斗。
看样子他们想用路障把电梯堵住。

船员在堵电梯。敌人在船里面。

指挥甲板上空无一人,只有正中央坐着一个矮小的身影:

流浪者的形态,但不太像血肉。也不太像金属。胸口嵌着一块水晶,脉动着光。
它说它早就预见了那场叛乱。说它做了安排。

打死它,掉一样东西:舰队信标。

带着信标起飞。

信标在轻轻搏动,飞船滑行在太空里。坐标已经锁定。什么也不用做,只要等。

信标亮起一片纯蓝,然后暗了下去。飞船慢了下来。
渐渐地,庞大的流浪者母舰队出现在视野里。
巨大的世界舰在残骸云里不自然地漂着,伤痕累累,全都死了。

空气快没了。

座舱很冷。

屏幕上只剩下一个按钮:

等。

按下去,画面淡出,分数出现,下面一行小字:重新开始。

后面

我把这些拆出来,不是想总结出一套方法论。这游戏很大程度上是不可复制的——它的极简是被”纯文字网页游戏”这个形式逼出来的,而那个形式本身在 2013 年是个异类。

但有两条我觉得是通用的。

一条是让玩家先做,再告诉他做了什么。游戏能做到而小说电影做不到的,就是让观众成为共犯。大部分游戏浪费了这个,还在用过场动画讲道理。

另一条是忍住不解释。把最关键的信息放进一个形容词,放进一个可选的支线,放进一个玩家从不设防的系统机制里。相信玩家自己能拼出来,也接受一部分人永远拼不出来。

代码那边还有一堆东西可以写——它怎么用五十行写出一个手搓的 Redux,怎么用”黏性概率”生成一张不像噪点的地图,怎么把十三万字符的剧情做成纯数据让非程序员也能写。留到下一篇。

项目地址:https://github.com/doublespeakgames/adarkroom,MPL-2.0。想直接玩的话:https://adarkroom.doublespeakgames.com

没有评论: