本文翻译自 Making Video Games in 2025 (without an engine),原载于 Hacker News。
2025年了,我还在做游戏——根据 archive.org 的记录,距离我开始做游戏已经整整20年。这么长时间只做一件事,确实挺长的。
当我分享正在开发的东西时,人们经常问我是怎么做游戏的。当我告诉他们我不使用商业游戏引擎时,他们往往会感到惊讶,有时甚至会担心。大家似乎有一种假设:不使用 Unity 或 Unreal 这样的大型工具,就等于要手写汇编代码,一条指令一条指令地写。
我真心认为,不使用「包办一切」的大型引擎来开发游戏,可以更简单、更有趣,而且往往开销更小。我做的不是「包办一切」的游戏,我也不需要这些引擎提供的 90% 的功能。我对游戏的感觉、外观以及我与工具的交互方式非常挑剔。我经常发现 Unity 等大型引擎的默认功能实现差强人意,最后还是得自己写。最终,我的项目大部分都是自己的工具和系统,引擎变成了只是一个漂亮的 UI 和一些渲染功能的载体……
到了这个地步,我为什么还要用这个引擎?它给我提供了什么?为什么我要让一个工具在我突然做出不道德、糟糕的商业决策时,有可能摧毁我的工作能力?或者他们推出一个在主机上运行游戏所必需的更新,但恰好破坏了我游戏中的整个系统,迫使我重写?为什么我要每天与这个东西斗争,而当我完成了对其默认系统的各种变通方案后,它本质上变成了一个花哨的资源加载器和编辑器 UI 框架?
对我来说,显而易见的答案就是:不使用大型游戏引擎,为我的特定用例编写自己的小型工具。这更有趣,而且我喜欢掌控我的开发技术栈。我知道当出问题时,我可以找到问题并解决它,而不是提交一个 bug 报告,3 个月后收到回复说「不会修复」。我喜欢知道在未来的又一个二十年后,我仍然能够编译我的游戏,而不需要盗版一个古老版本的残缺游戏引擎。
显然,这是我的个人偏好——一个长期制作独立游戏的人的偏好。我在转向更轻量级和自定义工作流程之前,使用 Game Maker 等引擎多年。我也在非常小的团队中工作,为团队成员制作一次性工具很容易。但我想反驳一下「从零开始」做游戏是什么不可能的大任务——尤其是在 2025 年,开源框架和库的状态已经非常成熟。很多流行的独立游戏都是用 FNA、Love2D 或 SDL 等小型框架制作的。「没有引擎」做游戏并不意味着打开一个纯文本编辑器写系统调用(除非你想这样)。通常,学习如何自己实现这些系统的开销,与学习引擎本身的专有工作流程一样耗时。
说了这么多,我觉得聊聊我的工作流程以及我实际用来做游戏的工具会很有趣。
编程语言
我职业生涯的大部分时间都在用 C#,除了几年前短暂使用 C++ 之外,我已经回归到现代 C# 工作流程。
我觉得有时候当我对非独立游戏开发者提到 C# 时,他们的脑海里会浮现出 2003 年左右的样子——一个闭源的、解释执行的、冗长的、垃圾回收的语言……但这个语言从那时起已经大大改进了。2025 年的 C# 与 2015 年的 C# 截然不同,许多变化都是针对语言的性能和语法。你可以在栈上分配动态大小的数组!C++ 都做不到这一点(虽然 C99 可以 ;) …)。
dotnet 开发人员还在 C# 中实现了热重载(大部分时候能工作),这对游戏开发来说非常棒。你可以用 dotnet watch 启动项目,它会实时更新代码更改,当你想改变某物的绘制方式或敌人的更新逻辑时,这简直太棒了。
C# 也成为了「运行快」和「日常易用」之间的绝佳中间地带。例如,我和弟弟 Liam 一起开发 City of None,当我们开始项目时他几乎没写过代码。但在过去一年里,他慢慢掌握了这门语言,已经能自己编程整个 Boss 战了,因为 C# 就是这么易用——而且几乎没有自毁陷阱。对于每个人都身兼数职的小团队来说,这是一门非常棒的语言。
最后,它有内置的反射……虽然我不会在发布代码中使用它,但能够快速反射游戏对象来制作编辑器工具是非常棒的。我可以轻松制作实时检查工具来显示游戏对象的状态,而不需要任何自定义元编程或游戏内反射数据。在 C++ 中做了几年游戏后,我真的很高兴能重新拥有这个功能。
窗口……输入……渲染……音频?
这是「从零开始写游戏」时的大问题,但有很多优秀的库可以帮助你把东西显示在屏幕上——从 SDL、GLFW、Love2D 到 Raylib 等等。
我一直在使用 SDL3,因为它作为跨平台系统抽象层可以满足我的所有需求——从窗口管理到游戏控制器再到渲染。它可以在 Linux、Windows、Mac、Switch、PS4/5、Xbox 等平台上运行,而且从 SDL3 开始,它有一个 GPU 抽象层,可以处理 DirectX、Vulkan 和 Metal 的渲染。它就是能工作,是开源的,被行业内很多人使用(比如 Valve)。我开始使用它是因为 Celeste 用来在非 Windows 平台上运行的 FNA 使用它作为平台抽象层。
话虽如此,我在 SDL 之上编写了自己的 C# 层,用于在项目之间共享的通用渲染和输入工具。我对如何构建游戏有非常主观的选择,所以我喜欢有这个小层来接口。它非常适合我的需求,但也有像 MoonWorks 这样功能齐全的替代品可以填补类似的位置。
在 SDL3 发布 GPU 抽象之前,我在编写自己的 OpenGL 和 DirectX 实现——这不简单!但这是一次很好的学习经历,也没有我预期的那么糟糕。不过,我非常感谢 SDL GPU,因为它是一个非常坚实的基础,将在数百万台设备上经受测试。
最后,音频方面我们使用 FMOD。这是我们工作流程中最后一个专有工具,我不太喜欢(尤其是当某东西停止工作你必须手动修补他们的库时),但它是最佳工具。如果你只是想播放声音,有更轻量级的开源库,但我与音频团队合作,他们想要对动态音频进行精细控制,FMOD 这样的工具是必需的。
资源管理
关于资源我没有太多可说的,因为当你自己搭建引擎时,你只需要在需要时加载你想要的文件,然后继续。对于我所有的像素艺术游戏,我会预先加载整个游戏,这「没问题」,因为整个游戏只有大约 20MB。当我开发 Earthblade(有更大的资源)时,我们会在启动时注册它们,然后只在请求时加载,在场景转换后释放它们。我们只是采用了最简单的、能完成工作的实现方案。
有时你会有需要在游戏使用前进行转换的资源,这种情况下我通常会写一个在游戏编译时运行的小脚本来完成任何所需的处理。就是这样。
关卡编辑器、UI……
总有一天我会写一个完全程序化的游戏,但在那之前,我需要工具来设计游戏内空间。有很多非常棒的现有工具,比如 LDtk、Tiled、Trenchbroom 等等。我在不同程度上使用过这些工具,它们很容易设置并在你的项目中运行——你只需要写一个脚本来获取它们输出的数据并在运行时实例化你的游戏对象。
然而,我通常喜欢为我的项目编写自己的自定义关卡编辑器。我喜欢让我的游戏数据直接与编辑器关联,而且我在功能上从不深入,因为我们需要的东西是特定但有限的。
但我不想写实际的 UI——编写文本框和下拉菜单不是我特别热衷的事情。我想要一种简单的方式来创建字段和按钮,就像你在 Unity 游戏引擎中编写自己的小型编辑器工具一样。
这就是 Dear ImGui 发挥作用的地方。它是一个轻量级、跨平台的即时模式 GUI 引擎,你可以轻松地将其放入任何项目中。上面的编辑器截图中的所有内容都使用了它,除了实际的「场景」视图,那是自定义的,因为它只是绘制我的关卡。有更功能齐全(和重量级)的替代品,但如果它对包括《塞尔达传说:王国之泪》在内的这些游戏足够好,那对我来说也足够好了。
使用 ImGui 使编写编辑器工具变得极其简单。我喜欢让我的工具直接从游戏中提取数据,而使用 ImGui 加上 C# 反射使这变得非常方便。我可以用几行代码循环遍历 C# 中的所有 Actor 类,并让它们在我的编辑器中可访问!对于更复杂的工具,有时编写自己的实现有点过度,这种情况下我会回退到使用为特定工作构建的现有工具(比如 Trenchbroom,用于设计 3D 环境)。
游戏移植……?
几年前我学习 C++ 的主要原因是因为我对可移植性的担忧。当时,在主机上运行 C# 代码并不简单,因为 C# 是「即时」编译的,这不是许多平台允许的。我们的游戏 Celeste 使用了一个叫 BRUTE 的工具将 C# IL(中间语言二进制文件)转译为 C++,然后为目标平台重新编译。Unity 有一个非常相似的工具做同样的事情。这行得通,但对我来说并不理想。我想要能够直接为目标平台编译我们的代码,所以学习 C++ 似乎是唯一真正的选择。
然而,从那时起,C# 在其 Native-AOT 工具链方面取得了令人难以置信的进展(这基本上意味着所有代码都是「提前」编译的——像 C++ 和 Rust 默认做的那样)。现在可以为所有主要主机架构编译 C# 代码,这太棒了。FNA 项目在这方面非常积极,促成了使用 C# 在所有主要平台上发布游戏。
最后,SDL3 有所有主要平台的主机移植版本。使用它作为你的平台抽象层(只要你小心处理系统调用的方式)意味着很多东西「就能工作」。
再见,Windows
最后,总结一下……我不再使用 Windows 开发游戏了(除了测试)。我觉得这符合我使用开源、跨平台工具和库的总体理念。我发现 Windows 越来越让人沮丧,他们的商业行为令人反感,他们的操作系统普遍缺乏优势。我成长过程中使用 Windows,但大约 3 年前我全面转向 Linux。坦白说,对于编程视频游戏,我一点也不怀念它。它不能提供任何我在 Linux 上不能更快、更优雅地完成的东西。
当然,有一些工作流程和工具在 Linux 上不能工作,这就是当前的现实。我也不完全脱离 Microsoft——我使用 vscode,我用 C# 写游戏,我在 github 上托管我的项目……但越多人日常使用 Linux,支持它的压力就越大,对开源替代品的支持也就越多。
(作为一个有趣的旁注,我这些天大部分游戏都在 Steam Deck 上玩,这意味着在我的 PC、游戏主机、网络服务器和手机之间,我总是在一个 Linux 平台上)
杂七杂八的想法
-
那 Godot 呢? 如果你处于需要大型游戏引擎提供的东西的位置,我绝对认为 Godot 是最佳选择。它是开源和社区维护的,消除了我对其他专有游戏引擎的很多问题,但它仍然不是我通常想要制作游戏的方式。我确实打算在未来为一些具体的想法尝试一下。
-
那 3D 呢? 我认为使用大型引擎对于 3D 游戏确实有更多的用武之地——但即使如此,对于任何我想做的 3D 项目,我都会自己搭建小框架。我想制作不需要非常现代技术的高度风格化游戏,我发现这相当直接(例如,我们在不到 2 周内几乎没有 3D 知识的情况下制作了 Celeste 64)。
-
我需要最好的花哨技术来实现我的游戏创意 那就用 Unreal!这没什么问题,但我的项目不需要那些功能(我会说我需要的大多数东西通常可以相当快地学会)。
-
我的整个团队都知道 [某游戏引擎] 将整个团队迁移到自定义东西的成本可能很高且耗时。我绝对是从较小/单人团队的角度来说这些的。但话虽如此,根据经验,我知道几家中型工作室正在转向自定义引擎,因为他们已确定使用专有引擎的潜在风险太高,而迁移和学习成本是值得的。我认为现在对于较大的团队来说,使用自定义东西比很长一段时间以来都更容易。
-
游戏特定工作流程 我加载 Aseprite 文件,让我的 City of None 引擎自动将它们转换为游戏动画,使用它们内置的标签和帧计时。该格式惊人地简单直接。当你编写自己的工具时,添加这样的东西真的很容易!
好了!
就这些!这就是我在 2025 年做游戏的方式!
我认为你应该在没有大型引擎的情况下做游戏吗?我的答案是:如果这听起来很有趣的话。
——Noel
核心要点总结
- 不使用大型引擎并不意味着从零开始 - 2025 年有成熟的开源库(SDL3、FNA、Love2D)可以作为基础
- 现代 C# 已经今非昔比 - Native-AOT、热重载、栈分配等特性使其成为游戏开发的有力选择
- SDL3 是跨平台开发的利器 - 统一抽象了窗口、输入、渲染和音频,支持所有主流平台
- Dear ImGui 大幅简化编辑器开发 - 配合 C# 反射,几行代码就能构建强大的工具
- 自主掌控技术栈的价值 - 避免「引擎公司作恶」风险,二十年后仍能编译你的游戏
- Linux 完全可以胜任游戏开发 - 作者已全面转向 Linux 三年,没有怀念 Windows
编者注:作者 Noel Berry 是《Celeste》的联合创作者。这篇文章从实践者的角度,展示了独立游戏开发的另一条道路——不被大型引擎绑架,而是构建适合自己的精简工具链。对于追求控制力和长期可持续性的开发者来说,这种思路值得借鉴。