Noel Berry Mastodon Bluesky Twitch Instagram GitHub Email

2025 年的游戏开发 (不使用引擎)

2025 年 5 月 18 日

现在是 2025 年,我还在做游戏,根据 archive.org 的记录,我已经做了 20 年游戏了! 做一件事这么长时间真是太久了...

Screenshot of my website circa 2011

2011 年左右的网站截图

当我分享我的工作成果时,人们经常问我如何制作游戏,当他们得知我不使用商业游戏引擎时,他们通常会感到惊讶(有时甚至担心?)。 在人们的固有印象中,不使用像 Unity 或 Unreal 这样的大型工具制作游戏,就好像你在手动编写自己的汇编指令一样。

我真心相信,不使用大型“全能”引擎制作游戏会更容易、更有趣,而且通常开销更低。 我不需要制作“全能”游戏,也不需要这些引擎提供的 90% 的功能。 我对游戏的感觉和外观以及我与工具的交互方式非常挑剔。 我经常发现像 Unity 这样的大型引擎中的默认功能实现非常欠缺,以至于我最终还是自己编写。 最终,我的项目最终主要是我自己的工具和系统,而引擎仅仅变成了提供良好 UI 和一些渲染的载体...

既然如此,我为什么要使用这个引擎? 它能为我提供什么? 为什么我要让一个工具来潜在地摧毁我的工作能力,当他们突然做出不道德和糟糕的商业决策时? 或者推送一个更新,要求我运行游戏在主机上,而这个更新恰好破坏了我游戏中的整个系统,迫使我重写它? 为什么我要每天与这个东西作斗争,而它最终变成了一个用于加载资源和编辑 UI 框架的工具,在我绕过他们的默认系统之后?

对我来说,显而易见的答案就是不要使用大型游戏引擎,而是为我的特定用例编写我自己的小型工具。 这样更有趣,而且我喜欢控制我的开发栈。 我知道当出现问题时,我可以找到问题并解决它,而不是提交一个错误报告,3 个月后才收到“不会修复”的回复。 我喜欢知道,从现在开始的 20 年后,我仍然可以编译我的游戏,而不需要盗版一个旧版本的损坏的游戏引擎。

显然,这是我个人的偏好——并且是一个长期从事独立游戏开发的人的偏好。 在过渡到更轻量级和自定义的工作流程之前,我使用了像 Game Maker 这样的引擎很多年。 我也在非常小的团队中工作,在那里很容易为团队成员制作一次性工具。 但我想反驳的是,从“零”开始制作游戏是一项不可能完成的任务——尤其是在 2025 年,有了开源框架和库的支持。 很多 流行的 独立 游戏 都是在 像 FNA,Love2D 或 SDL 这样的小型框架中制作的。 “不使用引擎”制作游戏并不意味着打开一个纯文本编辑器并编写系统调用(除非你想这样做)。 通常,学习如何自己实现这些系统的开销与学习引擎本身的专有工作流程一样耗时。

说了这么多,我认为谈谈我的工作流程以及我实际用来制作游戏的工具会很有趣。

编程语言

我的大部分职业生涯都在使用 C#,除了几年前短暂使用 C++ 之外,我还是回到了现代 C# 工作流程。

我认为有时当我向非独立游戏开发者提到 C# 时,他们会想到 2003 年左右的 C# ——一个闭源、解释型、冗长、垃圾回收的语言,而且……自那以后,该语言已经_大大_改进了。 2025 年的 C# 与 2015 年的 C# 截然不同,许多更改都面向语言的性能和语法。 你可以在堆栈上动态分配大小的数组! C++ 做不到 (尽管 C99 可以 ;) ...)。

dotnet 开发人员还在 C# 中实现了热重载(它_大部分时间_都能正常工作),而且它对于游戏开发来说非常棒。 你可以用 dotnet watch 启动你的项目,它会实时更新代码更改,这在你想要更改某些东西的绘制方式或敌人更新的方式时非常有用。

C# 最终成为了运行速度快(视频游戏需要)和易于日常使用的绝佳折衷方案。 例如,我一直在和我的兄弟 Liam 一起开发 City of None,他在我们开始这个项目时几乎没有编写过代码。 但在过去的一年中,他慢慢地掌握了这门语言,以至于他自己编写了整个 boss 战,因为 C# 就是这么容易上手——而且相当安全。 对于每个人身兼数职的小团队来说,这是一种非常好的语言。

A boss fight that Liam coded Liam 编写的 Boss 战

最后,它内置了反射... 虽然我不会将其用于发布代码,但能够快速反射游戏对象以进行编辑器工具化非常有用。 我可以轻松制作实时检查工具,显示游戏对象的状态,而无需任何自定义元编程或游戏内反射数据。 在用 C++ 制作了几年游戏之后,我真的很喜欢重新拥有这个功能。

Inspecting an object with reflection in Dear ImGui

在 Dear ImGui 中使用反射检查对象

窗口... 输入... 渲染... 音频?

这是在从“零”开始编写游戏时遇到的一个大问题,但是有很多很棒的库可以帮助你将内容显示在屏幕上——从 SDLGLFWLove2DRaylib 等。

我一直在使用 SDL3,因为它完成了我作为系统跨平台抽象所需要的一切——从窗口化到游戏控制器再到渲染。 它适用于 Linux、Windows、Mac、Switch、PS4/5、Xbox 等,并且从 SDL3 开始,有一个 GPU 抽象,可以处理跨 DirectX、Vulkan 和 Metal 的渲染。 它 开箱即用,是开源的,并且被许多行业使用(例如 Valve)。 我开始使用它是因为 FNA(Celeste 使用它在非 Windows 平台上运行)使用它作为其平台抽象。

也就是说,我已经编写了 我自己的 C# 层,放在 SDL 之上,用于我在项目中共享的通用渲染和输入实用程序。 我对如何构建我的游戏做出了非常主观的选择,因此我喜欢拥有这个小层来进行交互。 它非常适合我的需求,但是也有功能齐全的替代方案,例如 MoonWorks,它们占据了类似的空间。

在 SDL3 发布带有 GPU 抽象之前,我一直在编写自己的 OpenGL 和 DirectX 实现——这并不容易! 但这是一个 很棒的学习经历,而且没有我想象的那么糟糕。 但是,我非常感谢 SDL GPU,因为它是一个非常坚实的基础,将在数百万台设备上进行测试。

最后,对于音频,我们正在使用 FMOD。 这是我们工作流程中的最后一个专有工具,我并不喜欢它(特别是当某些东西停止工作 并且你必须手动修补他们的库时),但它是完成这项工作的最佳工具。 如果你只是想播放声音,还有更轻量级的开源库,但我与希望完全控制动态音频的音频团队合作,而像 FMOD 这样的工具是必需的。

资源

关于资源我没什么好说的,因为当你推出自己的引擎时,你只需在你需要的时候加载你想要的文件,然后继续前进。 对于我所有的像素画游戏,我都会预先加载整个游戏,而且“没问题”,因为整个游戏只有 20mb 左右。 当我开发 Earthblade 时(它具有更大的资源),我们会在启动时注册它们,然后仅在请求时加载它们,并在场景过渡后释放它们。 我们只是采用了最简单的实现来完成这项工作。

Assets loading in 0.4 seconds

City of None 的所有资源在 0.4 秒内加载完毕

有时你会有一些需要在游戏使用之前进行转换的资源,在这种情况下,我通常会编写一个小脚本,在游戏编译时运行,以执行所需的任何处理。 就是这样。

关卡编辑器、UI...

总有一天我会编写一个完全程序化的游戏,但在那之前,我需要工具来设计游戏中的空间。 有很多非常棒的现有工具,例如 LDtkTiledTrenchbroom 等。 我在不同程度上使用过许多这些工具,它们很容易设置并在你的项目中运行——你只需要编写一个脚本来获取它们输出的数据并在运行时实例化你的游戏对象。

但是,我通常喜欢为我的项目编写自己的自定义关卡编辑器。 我喜欢让我的游戏数据直接与编辑器相关联,而且我从不在功能上深入研究,因为我们需要的东西是特定的但有限的。

A small custom level editor for City of None using Dear ImGui

使用 Dear ImGui 为 City of None 制作的小型自定义关卡编辑器

但是,我不想编写实际的 UI——编码文本框和下拉列表不是我特别热衷的事情。 我想要一种创建字段和按钮的简单方法,有点像 在 Unity 游戏引擎中编写自己的小型编辑器实用程序 一样。

这就是 Dear ImGui 的用武之地。 它是一个轻量级、跨平台的即时模式 GUI 引擎,你可以轻松地将其放入任何项目中。 上面的编辑器屏幕截图使用它来处理所有内容,除了实际的“场景”视图,它是自定义的,因为它只是绘制我的关卡。 还有功能更齐全(且更重型)的替代方案,但如果它对所有这些游戏(包括 Tears of the Kingdom)来说足够好,那么它对我来说也足够好。

使用 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#。

Platforms supported by SDL3

SDL3 支持的平台

最后,SDL3 具有所有主要平台的主机端口。 将其用作平台抽象层(只要你小心处理系统调用),意味着很多东西都可以“正常工作”。

再见,Windows

最后,总结一下……我不再使用 Windows 来开发我的游戏(除了测试)。 我觉得这符合我使用开源、跨平台工具和库的一般理念。 我发现 Windows 越来越令人沮丧,他们的 商业行为令人厌恶,而且他们的操作系统总体上缺乏。 我从小就使用 Windows,但大约 3 年前我完全切换到了 Linux。 坦率地说,对于编程视频游戏,我根本没有错过它。 它并没有为我提供任何我不能在 Linux 上更快、更优雅地完成的事情。

当然,某些工作流程和工具在 Linux 上不起作用,这就是当前的现实。 我也没有完全摆脱 Microsoft——我使用 vscode,我用 C# 编写我的游戏,并且我在 github 上托管我的项目……但是,每天使用 Linux 的人越多,支持它的压力就越大,对开源替代方案的支持也就越多。

(作为一个有趣的题外话,我现在在我的 Steam Deck 上玩我的大部分游戏,这意味着在我的 PC、游戏主机、Web 服务器和手机之间,我始终处于 Linux 平台上)

其他想法

Celeste 64 screenshot

Celeste 64 截图

Screeshot of Aseprite

Aseprite 资源自动加载

我加载 Aseprite 文件,并让我的 City of None 引擎使用其内置标签和帧定时自动将它们转换为游戏动画。 该格式 出乎意料地简单明了。 当你编写自己的工具时,很容易添加这样的东西!

好的!

这就是我的一切! 这就是我在 2025 年制作游戏的方式!

你是否认为你应该在不使用大型引擎的情况下制作游戏? 我的答案是:如果听起来有趣的话。

-Noel ★ 感谢你查看我的网站 ★ 你可以在这里找到源代码