少数规则,长出很多玩法
《塞尔达传说·旷野之息》里没有人写过「用火把点燃草地产生上升气流然后滑翔过去」这条规则。它是「火点燃草」「热空气上升」「滑翔伞借助上升气流」三条独立规则自己撞出来的。这就是涌现。这一章讲清楚它的三个必要条件,以及它真实的账单 —— 因为涌现从来不是免费的。
两条不同的路
产生玩法内容,历史上只有两条路:
| 编排(Designed) | 涌现(Emergent) | |
|---|---|---|
| 做法 | 设计师一个一个摆出来 | 定几条规则,让它们互相作用 |
| 内容量 | 正比于你的工时 | 可能远超你的工时 |
| 可控性 | 完全可控 | 你自己也会被惊到(有好有坏) |
| 可测试性 | 能穷举 | 不能穷举,必然有你没想到的情况 |
| 教学 | 容易,一个一个教 | 难,你没法教一个你没预见的组合 |
| 典型 | 马力欧的关卡、传送门的谜题 | 我的世界、塞尔达、缺氧、Dwarf Fortress |
先看一眼涌现长什么样。下面这个沙盘只有四条规则,每条一行代码。点画面可以点火。
建议这样试:先只开「火会蔓延」—— 火会烧光一切,然后停下,很无聊。加上「会烧尽」—— 火有了生命周期,出现了推进的火线。再加「风」—— 火有了方向,会绕开、会包抄。最后开「灰烬重新长草」—— 整片草地进入一个永不停歇的生灭循环。
四条一行的规则,你已经预测不了三十秒后的画面了。而你没有写过任何一条「火线会包抄」的代码。
涌现的三个必要条件
不是随便几条规则堆一起就能涌现。大多数人的第一次尝试会得到一堆互不相干的系统各干各的 —— 那不是涌现,那只是很多个功能。
如果规则 A 改变「温度」,规则 B 读取「温度」,它们就能咬合。如果 A 改变「火焰状态」而 B 读取「燃烧标记」,它们就永远不会相遇 —— 哪怕语义上是一回事。
所以涌现系统的第一个设计决定是:确定一小组共享属性,然后让所有规则都通过它们说话。
塞尔达的那组属性大概是:燃烧、导电、结冰、重量、材质。所有物体都有这几个属性,所有规则都只读写这几个属性。这才是那台「化学引擎」的真身 —— 不是规则多,是所有规则说同一种语言。
正交的意思是:每条规则只管一件事,不知道别的规则的存在。
- ✓ 正交:「火会点燃可燃物」「木头是可燃物」「箭矢可以携带火」
- ✗ 特判:「火箭射到木箱时,木箱燃烧」
两者在「火箭烧木箱」这一个场景下效果完全一样。但正交的版本白送了:火箭烧草、火箭烧树、火把点木箱、雷劈木箱(如果雷也产生火)……而特判的版本,每一种你都得再写一遍。
特判是涌现的天敌。每写一个 if (a == 火箭 && b == 木箱),你就杀死了一批本可以免费出现的组合。
这条最容易被忘掉,但它决定了涌现有没有意义。
如果玩家不知道「火会烧草」,那他永远不会想到去烧草。玩家没意识到的涌现,等于不存在。
所以涌现系统对教学和反馈可见性的要求,比编排系统更高而不是更低。塞尔达花了大量力气让每条规则在早期就被看见:草会自己被火点着(不用你做什么)、金属武器在雷雨天会闪电(还给你个提示音)、木头武器烧掉时给你一段特写。
涌现的账单
网上聊涌现总是很浪漫。这一节说点不浪漫的。
账单一:你没法测试它
编排的关卡,你可以一个一个玩过去,确认每一个都能通、都不卡。涌现系统状态空间是组合爆炸的,你永远测不完。
后果是:玩家一定会撞到你没想到的组合。其中有些很棒(成为口碑传播的素材),有些会卡死你的游戏或者让某关变得不可能通过。
《缺氧》和《Dwarf Fortress》都长期有这类问题。这是这条路的固有成本,不是能力问题。
账单二:涌现的乐趣需要玩家「会玩」
涌现系统的乐趣在于玩家自己发现组合。但这要求玩家主动去试。
而大多数玩家 —— 尤其是前二十分钟的玩家 —— 不会主动试。他们在等游戏告诉他们该干什么。如果你的游戏在前二十分钟里只是把一堆规则摆在那,多数人会觉得「不知道要干嘛」然后退款。
这是沙盒 / 涌现类游戏最常见的死法:前 20 分钟没有目标。
《我的世界》最初也有这个问题,它靠一个非常朴素的东西解决了:天要黑了,会有怪。—— 一个所有人都能立刻理解的目标(造个房子),把玩家推进系统里。等他们造完房子,已经在系统里待了半小时,自己就开始玩了。
所以:纯涌现的系统,也需要一个编排的开局。
账单三:涌现不等于深度
这是最贵的一课,很多人做到一半才明白。
你可以做一个模拟得极其精细的生态系统 —— 每只动物有饥饿度、繁殖周期、领地范围、捕食关系。跑起来数据很漂亮。
然后玩家玩了半小时,说:「这游戏就是站着看动物走来走去。」
问题在于:涌现产生的复杂度,如果玩家的动词碰不到它,那它对玩家来说就是背景动画。
玩家的动词,必须能作用在产生涌现的那组共享属性上。
- 塞尔达:玩家能点火、能放置金属物、能冻结水面 —— 他的动词直接操作那几个共享属性,所以他能参与化学反应。
- 那个生态模拟:玩家只能看。他的动词碰不到饥饿度和领地范围,所以再复杂的模拟也和他无关。
修法很简单:给玩家一个能拨动系统的杠杆。哪怕只有一个 —— 能放置食物、能筑一道墙、能引入一个新物种。一个杠杆,整个模拟立刻从背景变成玩法。
涌现系统怎么调试
「测不完」这笔账,实践中具体是怎么应付的?有几个真的管用的手段。
涌现系统最难的地方是你看不见状态。屏幕上一片火,但哪些格子是「正在燃烧」、哪些是「即将被点燃」、每个格子还剩多少燃料?
做一个调试叠加层,把关键属性直接画在物体上:温度用颜色、剩余燃料用数字、即将触发的规则用高亮。这是涌现系统里性价比最高的一次投入 —— 没有它,你只能靠猜。
本页那个 demo 里的统计条(燃烧中 / 草 / 灰烬 / 帧)就是最小版本的这个东西。
你测不完所有组合,但你可以让游戏自己在出问题时喊出来。
找出那些「无论如何都该成立」的性质,写成断言,在调试版里每帧检查:
func _assert_invariants() -> void:
if not OS.is_debug_build():
return
# 水永远不该在燃烧
for cell in _burning_cells:
assert(cell.type != Cell.WATER, "水着火了:%s" % cell)
# 每个物体最多属于一个状态组
for obj in get_tree().get_nodes_in_group("flammable"):
assert(not (obj.is_burning and obj.is_frozen), "既在烧又结冰:%s" % obj)
这些断言不会替你找到「有趣的意外」,但会替你抓住「破坏性的意外」 —— 而后者才是真正会毁掉玩家存档的那类。
写一个脚本,让它随机地在世界里乱来:随机点火、随机放置、随机移动,跑一万步,看会不会崩、会不会出现非法状态。
这听起来粗暴,但对涌现系统极其有效 —— 因为它探索的正是你想不到的那些组合。而且它可以挂在后台跑一整晚。
配合第 15 章的固定种子:崩溃时记下种子,你就能精确复现那次崩溃。
还有一类问题:不可通关
涌现系统在关卡里还有一个特殊风险:玩家可能把局面搞成无法通关的状态,而游戏不告诉他。
比如:他把唯一能垫脚的箱子推进了深坑,现在够不到那个平台了 —— 但游戏还在继续,他要花十分钟才意识到自己卡死了。
三条常见对策:
- 不可逆的操作要可撤销 / 可重置。给一个「重置本房间」的键 —— 这是最便宜也最有效的一条
- 关键道具要能被找回。掉进坑里的箱子会重新出现在原位
- 或者干脆检测死局并提示玩家(成本最高,通常不值得)
第一条几乎总是最优解。一个「R 键重置房间」,能省掉你为所有死锁情况写检测的工作量。
那我该选哪条路
对于你的第一个游戏,答案很清楚:以编排为主,在一两个点上留一小片涌现。
理由是风险结构。编排的工作量可预测、能测试、能保证质量下限。涌现的上限很高但下限没保障,而第一个项目最需要的是确定能做完。
你不需要在「全编排」和「全涌现」之间二选一。最划算的做法是在编排的骨架里,放两三条会互相作用的规则。
例子(一个平台游戏):
- 规则 1:箱子会被推动,并且受重力
- 规则 2:压力板被压住时门会开
- 规则 3:敌人走路不看脚下
三条正交规则,白送了这些玩法:推箱子压板、把箱子推下去砸敌人、把敌人引到压力板上帮你开门、用箱子垫高跳上去、敌人自己走上压力板造成意外……
三条规则,十几种解法,而关卡还是你一手编排的 —— 你完全控制得住,但玩家会觉得这个世界是活的。
更妙的是:玩家用你没想到的方法通关时,他会觉得自己很聪明,而且会去告诉别人。这是最好的口碑素材,而它几乎是免费的。
写代码时的一条纪律
无论你选哪条路,有一条纪律现在就该养成:
能用规则表达的,不要用特判。
每次你想写 if (这个特定的东西 碰到 那个特定的东西),先停三十秒,想想能不能改成两个属性的一般规则。
把「是什么」换成「有什么属性」,这是让涌现成为可能的工程写法。
# ✗ 特判:每加一种新东西,这个函数就要改一次
func _on_fire_touched(body: Node) -> void:
if body is WoodenCrate:
body.burn()
elif body is GrassPatch:
body.burn()
elif body is Player:
body.take_damage(5)
# …… 每加一种可燃物,就要回来改这里
# ✓ 规则:火只认识「可燃」这个属性,不认识具体是谁
func _on_fire_touched(body: Node) -> void:
if body.is_in_group("flammable"):
body.ignite()
# 每个可燃物自己实现 ignite(),火不需要认识它们
extends StaticBody2D
func _ready() -> void:
add_to_group("flammable")
func ignite() -> void:
burning = true
# 关键一步:燃烧的东西自己也变成火源
# —— 这一行让「蔓延」这个行为免费出现了
$FireArea.monitoring = true
注意第二段最后那两行。让燃烧的物体自己也成为火源,你就免费得到了「火会蔓延」 —— 而且是对所有可燃物、所有场景、所有你还没做出来的东西都成立的蔓延。
这就是正交规则的复利:你写了三行,得到的行为随着物体种类增加而自动增长。
本章小结
- 两条路:编排(可控、可测、内容正比工时)和涌现(上限高、不可控、不可穷举测试)。
- 涌现的三个必要条件:共享属性、规则正交(不特判)、玩家看得见规则。
- 三笔账单:测不完、需要玩家主动试(所以要有编排的开局)、涌现 ≠ 深度。
- 涌现有效的判据:玩家的动词能不能作用在产生涌现的那组属性上。碰不到就是背景动画。
- 第一个游戏的最优解:编排为骨架 + 两三条正交规则。便宜、可控,但玩家会觉得世界是活的。
- 工程纪律:用属性和 group,不用类型判断。特判是涌现的天敌。
- 卡牌 / 肉鸽:这类游戏的涌现全部来自卡牌之间的组合,共享属性就是那几个关键词(易伤、力量、抽牌、能量)。关键词做得越少越通用,涌现越强 —— 二十个各自独一无二的效果,不如五个能互相叠乘的关键词。
- 塔防:涌现来自塔与塔之间的相互作用(减速塔让伤害塔打得更久、毒塔和爆炸塔连锁)。设计要点同上:让塔操作共享的状态(敌人身上的减速 / 中毒 / 易伤),而不是各自算各自的伤害。
- 模拟经营:涌现的原产地,也是踩「涌现≠深度」这个坑最多的类型。反复自查:玩家的杠杆够不够多?如果玩家只能看着系统跑,那不管模拟多精细都是屏保。
- 叙事解谜:通常不应该追求涌现 —— 这类游戏的价值恰恰在于精心编排的「啊」的瞬间,而涌现会让那个瞬间失控。但可以在小范围用:一个有几条物理规则的谜题房间,允许多解,会让玩家非常有成就感。
- 平台 / 动作:涌现主要发生在玩家能力之间(蔚蓝的冲刺 × 蹬墙 × 抓取)。这里的正交纪律尤其重要:每个能力都要能和其他所有能力组合,而不是各自只在指定关卡里用。