本文翻译自 The gold standard of optimization: A look under the hood of RollerCoaster Tycoon,原载于 Hacker News。
最近有幸参加了德国最大的游戏播客之一「Stay Forever」,聊了聊《过山车大亨》(RollerCoaster Tycoon,1999)的技术实现。这是一次很棒的访谈,强烈推荐德语听众去听听完整节目。
《过山车大亨》及其续作经常被 cited 为史上优化最好的游戏之一——由 Chris Sawyer 几乎完全用汇编语言(Assembly)编写。这款游戏在 1999 年的硬件上,居然能流畅模拟拥有数千个游客的完整主题公园,着实令人惊叹。要知道,即使到了今天,很多类似的建造类游戏仍然难以保持稳定的帧率。

那么,Chris Sawyer 究竟是如何做到的?
使用汇编语言
最常被提到的原因是游戏用汇编语言编写。在游戏开发早期,汇编语言是标准做法,但到了 1999 年,这已经是一种几乎被放弃的实践。即使比它早六年发布的初代《毁灭战士》(Doom),大部分代码也已经是用 C 语言编写,只有少数关键部分使用汇编。
虽然很难确定,但《过山车大亨》很可能是最后一款大规模使用汇编开发的商业游戏。在当时,使用汇编带来的性能提升可能比现在更显著——现代编译器已经非常擅长优化高级语言代码,许多当年需要手动完成的优化,现在编译器都能自动处理。
金钱的类型设计
你会如何在游戏中存储金钱数值?通常的做法是考虑游戏中可能需要的最大金额,然后选择合适的数据类型。Chris Sawyer 的做法更加精细:
- 存储公园总价值的变量使用 4 字节(因为公园价值可能达到很高的数字)
- 商店商品的可调价格?只需要 1 字节就够了
这种精细的类型选择在 OpenRCT2(开源重制版)中已经被移除,改为统一使用 8 字节变量——因为现代 CPU 上这已经不会造成性能差异了。
用位运算替代数学运算
在阅读 OpenRCT2 的源码时,你会频繁看到这样的语法:
value = value << 2;
这行代码做的事情和大多数人会写的 value = value * 4; 是一样的。<< 操作符执行的是位左移,把变量的所有二进制位向左移动两位,新位置用零填充。在二进制系统中,每左移一位就相当于数值翻倍。
这个技巧听起来可能有点技术宅,但其实我们在十进制中也在做类似的事情。当你计算 57 × 10 时,你是真的在「计算」乘法吗?还是直接在 57 后面加个 0?同样的原理,只是进制不同。
同样的技巧也可以用于除法:
value = value >> 1; // 相当于 value = value / 2;
《过山车大亨》到处都在用这个技巧,即使是 OpenRCT2 版本也保留了这种写法,因为编译器不会自动做这种优化。
更有趣的是,位运算只能用于 2 的幂次方的乘除法(如 2、4、8、16 等)。代码中频繁使用这种技巧说明:游戏内的公式是专门设计成尽可能使用这些数字的。
在现代开发流程中,这几乎是不可能的。想象一下,程序员去问游戏设计师:「能不能把公式里的 9.5 改成 8?因为 CPU 更喜欢计算这个数字。」在大多数公司,游戏设计师一辈子都不应该担心二进制算术的运行时性能特性——那是程序员的事。但《过山车大亨》的游戏设计师和程序员是同一个人。
为性能而生的游戏设计
《过山车大亨》并不完全是一个「单人项目」——游戏的图形由 Simon Foster 创作,音效由 Allister Brimble 负责。但 Chris Sawyer 既是主程序员,也是唯一的游戏设计师。
这种角色的重叠使得一种深层次的优化成为可能:根据性能特征来设计游戏机制。
路径寻找的巧妙设计
一个绝佳的例子是游戏中的寻路系统(pathfinding)。
在设计公园建造游戏的文档时,很容易设计出这样的方案:游客先决定想去哪个景点(基于个人偏好),然后找到前往该景点的路径。

但从技术角度来看,这设计简直是性能噩梦。寻路是一项昂贵的计算任务,同时为数千个智能体运行寻路算法,即使在现代机器上也是令人头疼的挑战。
所以,《过山车大亨》的游客行为从根本上就不同:
游客不是先选择目标再找路,而是「盲目」地在公园里闲逛,等待偶然发现有趣的游乐设施。 他们沿着当前路径走,根本不考虑游乐设施或需求。当到达岔路口时,他们几乎是随机选择新的方向,只使用极少量的额外规则来避免死胡同等。
在游戏中仔细观察一个游客,你很容易发现这个「缺陷」:他们走路毫无目的。即使抱怨又饿又渴,他们也不会主动寻找最近的食物摊位,而是继续闲逛直到偶然经过一个。
当然,《过山车大亨》并非完全不做寻路。有些情况仍然需要传统寻路:
- 维修工需要到达损坏的游乐设施
- 游客想要离开公园
但即使在这些情况下,游戏也有安全网来避免帧率骤降。最重要的是,寻路器有一个内置的搜索深度限制。如果在达到限制前还没找到路径,寻路器可以直接取消搜索并返回失败。
作为玩家,你可以实时看到寻路失败——通过阅读游客的想法:

没错,每当游客抱怨找不到出口,本质上就是寻路器在说:「可能存在一条路径,但为了性能,我就不再继续找了。」
这部分让我特别着迷:它把出于技术必要性所做的优化,变成了一个游戏特性。在现代游戏开发中,这种事情几乎不可能发生,因为程序员和游戏设计师的角色是严格分离的。
更妙的是,还有更多游戏系统与之关联:
- 默认情况下,寻路器最多遍历 5 个岔路口的深度
- 维修工比普通游客更重要,所以他们可以搜索 8 个岔路口的深度
- 如果游客买了公园地图(在信息亭出售),他们的寻路限制会从 5 增加到 7
拥挤但不拥堵
另一个例子是《过山车大亨》如何处理拥挤的公园。
拥挤的道路在每个主题公园都很常见,游戏显然也需要处理这种情况。但显而易见的解决方案——实现某种智能体碰撞或避让系统——对帧率的伤害就像氪石对超人的伤害一样大。

解决方案?直接绕过这个技术挑战。
《过山车大亨》中的游客不会互相碰撞,也不会互相避让。实际上,数千名游客可以同时占据同一个路径格子:
当然,这并不意味着玩家不需要考虑拥挤问题。虽然游客不会与周围的其他游客互动,但他们会「感知」到周围的人数。如果附近有太多其他游客,这会影响他们的快乐度并触发对玩家的抱怨。对玩家来说,结果是一样的——他们仍然需要规划布局来避免过于拥挤的道路。但这种实现方式所需的计算量要小几个数量级。
启示与总结
《过山车大亨》可能是这种特定优化方法的「完美风暴」,但这并不意味着现在不能这样做。它只是需要程序员和游戏设计师之间更多的对话,以及面对技术挑战时说「不」的勇气。
这篇文章给我们的启示:
-
极致优化需要全栈思维:Chris Sawyer 既是程序员又是设计师,这种角色重叠使得性能优化可以深入到游戏设计层面,而不仅仅是代码层面。
-
有时最好的优化是重新设计问题:不是解决「如何高效地为数千人做寻路」,而是问「我们真的需要寻路吗?」
-
技术限制可以变成游戏特性:游客找不到出口的抱怨,本质上是性能优化的副作用,但它增加了游戏的真实感和挑战性。
-
现代开发流程的权衡:角色分工虽然提高了效率,但也失去了这种深度优化的可能性。也许在某些项目中,值得重新思考这种分工。
-
编译器的进步:当年需要手动做的许多优化,现代编译器已经能自动处理。但有些优化(如位运算替代乘除)编译器仍然不会自动做,因为会影响边界情况的处理。
如果你对游戏开发技术感兴趣,强烈推荐去看看 OpenRCT2 的源码,它是了解这款传奇游戏技术实现的绝佳窗口。