放弃 Rust:一次游戏引擎迁移的经验分享
放弃 Rust
BrandonReinhart · 3 天前 · 开发日志
2023 年 12 月,我开始构建 Architect of Ruin,当时选择使用 Bevy 游戏引擎。我的选择是出于对 Rust 个人兴趣——我非常享受使用这门语言的过程。 此外,Bevy's ECS 模型也让我觉得很有趣,并且我对 Bevy 社区的开放性表示真诚的赞赏。
因此,在 2025 年 1 月,我们将游戏从 Rust 和 Bevy 迁移出去,这让人有些惊讶。 我花了大约六周的时间用 C# 完全重写了游戏,并且在过去的三个月中一直在使用 Unity。
切换引擎是一个经典的“项目杀手”。 生产力可能会骤降,回归问题不可避免地出现,而且每向前一步似乎都会导致后退三步。 更不用说,在一个语言和引擎中积累的领域专业知识无法完全转移到新的语言和引擎中。
但我还是咬紧牙关做了这个决定,我想解释一下原因。
Bevy 之旅
在 Bevy 中完成了许多出色的工作。 在 Bevy 中实现了瓦片地图、我构建场景的大部分方法,以及大量的角色和游戏逻辑。 我通过拆解 Spine 运行时的 Rust transpiles,学习了 Spine 和骨骼动画的内部原理。 我通过在 Bevy 的渲染世界中实现我自己的渲染功能,了解了很多关于自定义渲染管线的内容。 Bevy 的纯 ECS 使用起来很愉快,Rust 的编译时检查意味着我可以快速且自信地重构大片代码。
Bevy 社区也是一个积极的灵感来源——不仅仅是关于如何使用引擎的想法,而是一个由建设者和贡献者组成的积极社区。 这个社区非常擅长对游戏开发感到兴奋,并且对辩论充满活力。
我有机会为许多社区 crates 贡献功能和修复,尽管这些贡献大多很小,并且侧重于推动我自身目标的工作。
尽管有这些积极的经验和取得的进展,但随着开发的继续,实际的挑战开始显现。
突发问题
我想首先声明,我早在这些挑战显现之前就预料到了其中许多挑战。 我知道,在早期开发生命周期中使用游戏引擎会带来独特的风险和成本。 我认为这些成本很可能是值得的并且可以克服的。 我对 Rust 和 Bevy 的热爱意味着我愿意承担一些其他游戏开发者可能会选择避免的痛苦。 我并没有盲目地陷入这些具体问题,但它们比我预期的更难啃。
协作 - 我和我的兄弟一起开始了该项目。 虽然他很聪明并且渴望学习,但他对编码来说还是新手。 在学习 Rust 的独特之处的同时,直接让他上手游戏开发,事实证明这是具有挑战性的。 我们发现自己面临着更陡峭的学习曲线,这减缓了他为游戏逻辑做出有效贡献的能力。
抽象 - 虽然我的最初动机是对 Rust 的享受,但项目的瓶颈越来越成为更高级别游戏机制的快速迭代。 随着代码库的增长,我们发现将游戏想法转化为代码并不像我们希望的那样直接。 Rust(强大)的底层关注点并不总是适合我们特定游戏架构中快速原型设计所需的高度灵活的高级脚本风格。 我发现我构建和发布有趣游戏玩法的动力比我使用 Rust 构建的愿望更强烈。
我曾预料到这会成为一个问题,但我没有预料到它会开始让我恼火并减慢项目速度的程度。
(一个相对简单的游戏功能签名。)
迁移 - Bevy 很年轻,变化很快。 每次更新都带来了令人难以置信的功能,但也带来了大量的 API 变动。 随着项目规模的增长,更新迁移的负担也随之增加。 在核心 Bevy 系统(例如精灵渲染)中,小的回归问题很常见,这导致了巨大的摩擦和意想不到的调试工作。
这件事在一个特殊的日子里达到了顶峰,那天我对新版本中出现的精灵渲染问题感到沮丧。 Blake 同时遇到了同样的问题,我们共同的沮丧情绪演变成了一种“掀桌子”的时刻。 他转向我说了一些类似于“这不应该发生,这种事情应该已经解决”的话,这引发了导致重新评估的对话。
重点不是那个具体的精灵问题,而是因为 Bevy 中的所有系统都可以进行调整和改进,所以所有系统都可能受到回归的影响。
学习 - 在过去的一年中,我的工作流程发生了巨大的变化,我经常使用 AI 来学习新技术、讨论方法和技术、审查代码等等。 C# 和 Unity API 的成熟度和大量稳定的历史数据意味着像 Gemini 这样的工具始终如一地提供高度相关的指导。 虽然 Bevy 和 Rust 发展迅速——这令人兴奋和鼓舞——但这种速度意味着 AI 知识滞后,从而降低了我期望从 AI 辅助开发中获得的效率提升。 随着更多现代工具支持模型的推出,这种情况可能会发生变化,但我发现这是一种干扰和意想不到的额外成本。
Modding - Modding 对我来说意义重大。 我最初是作为一名 modder 进入这个行业的,我希望我的游戏具有高度的可修改性。 随着时间的推移,当我了解到更多关于如何实现这一目标的信息时,我开始了解到 Rust 和 Bevy 中的许多固有局限性,这些局限性会使这项任务更加困难。 缺乏明确的脚本解决方案和不稳定的 ABI(应用程序二进制接口)引起了人们的担忧。 我不是这方面的专家,也许这些都很容易克服。 我只能说,(经过大量搜索后)我没有找到一条让我有信心信任的道路。
这些因素结合在一起——对跨经验水平的更流畅工作流程的渴望、对游戏玩法的高级抽象的需求、优化生产力和 Modding——指向对项目下一阶段的重新评估。
切换
老实说,在开始这个项目时,我完全忽略了 Unity。
其中一些源于 Unity 方面的一些非受迫性错误。 他们刚刚经历了一场定价危机,最终导致他们的 CEO 辞职,他们似乎与独立开发者脱节。 我也做了一些假设。 我厌倦了用存在于旧游戏引擎中的过时形式的 C++ 进行编码,并认为我对 C# 也会有类似的感觉。 我认为由于 Unreal 没有为 2D 渲染管线提供太多东西,Unity 也不会提供。 这导致我在 2023 年没有认真考虑使用 Unity。
在 2025 年 1 月的第一周,Blake 和我决定进行成本效益分析。 我们写下了所有选项:Unreal、Unity、Godot、继续使用 Bevy 或推出我们自己的。 我们编写了大量的优缺点,强调了每个选项在上述标准下的表现:协作、抽象、迁移、学习和 Modding。
有了一些其他选项的经验,我决定需要更好地了解 Unity。 一个下午的研究让我得出结论,它似乎在优点方面比缺点高。
我们举行了一次团队会议,我在会上阐述了权衡。 Ulrick 指出,诸如粒子之类的一堆未知数将在打包引擎中得到解决。 Blake 指出,如果事情进展顺利,并且新引擎意味着更快的游戏开发,我们最终可能会提前完成。
10% 换 90%
团队决定投资进行一项实验。 我会选择三个核心功能,看看在 Unity 中实现它们有多困难。 我们将花费不超过 3 周的时间来完成这项任务。 我们将投入 10% 的精力,看看是否应该将剩余的 90% 投入到完整的移植中。
Tilemap - 我认为这会很简单,因为基本逻辑很简单。 它需要实现自定义着色器。 我们不会使用内置的 Unity Tilemap,因为我们的需求是特定的并且我们很清楚。 这是游戏场景的基础,我有一个很好的心理模型,了解我第一次用 Rust 编写它花费了多长时间。
角色 - 我们的角色使用 Spine,并具有独特的自定义要求和功能。 这在 Rust 中给我带来了很多麻烦,所以我认为这将在 C# 中成为一个很好的比较点。
UI - 我希望 UI 易于构建、快速迭代且可修改。 这是我们在 Rust 中学到很多东西的领域,并且再次有一个很好的心理模型可供比较。 一些研究让我得出结论,Noesis 将是一个不错的选择,因为它强调数据驱动的 XAML,并且 WPF 模型有非常完善的文档。 即使我不了解 WPF,我也知道我可以在 AI 的帮助下快速学习它。
选择前两个任务:Tilemap 和角色,是因为它们是基本的,但也可以让我检查我在一个简单任务和一个困难任务上的时间预期是否符合现实。 这将允许我更广泛地预测未来任务和移植的工作量。 选择 UI 任务是因为我们的游戏 UI 繁重,并且迭代 UI 的任何显着速度提升都将对未来的开发产生复合回报。
我们三天就完成了所有三项任务!
第一次提交是在 1 月 8 日,并且 Tilemap 在同一天完成。
在我实现 tilemap 时,Blake 编写了相机系统。 这证明了他的贡献能力显着提高,因为技术框架更易于理解。 这也极大地增强了他的信心,并带来了一种新的动力感。 我应该指出的是,Blake 以前从未编写过 C#。
我在 Unity Shader Graph 中实现了 Tilemap 着色器,认为这对于 Ulrick 来说更容易使用。 这不是我写的最后一个 Shader Graph,但最终,我决定视觉着色器的创建和迭代太慢了,并且重构也慢得多。 我现在用 HLSL 编写所有着色器。
在 1 月 10 日,我们弄清楚了在 Noesis 中构建 UI 的基础知识。 Blake 编写了一些简单的 UI 小部件,然后构建了主菜单,我构建了游戏 HUD 的第一部分,即工具栏。
这项工作比我预期的顺利得多,而且没有留下任何东西。 tilemap 花了一天的时间,Noesis 中的一个基本面板花了一个下午,包括连接插件。 剩余的时间都用于连接角色。 需要明确的是,我没有在一个下午内移植整个 UI 或实现我们的装备系统。 我们拥有的是可定制的角色身体、完全移植的 tilemap、一些基本菜单以及足够的知识来预测移植剩余部分需要多长时间。
此图像非常代表我们在移植的第一周结束时的位置,尽管实际上这是几天后放入项目后的图像。
在周末,我们召开会议讨论了我们学到的东西,并决定继续进行完整的移植。
接下来的六周专门用于将剩余的系统和内容从 Bevy 版本重写为 Unity/C#。 整个过程在很大程度上验证了我们三天测试的结果。 具有相同数量功能的游戏系统可以用更少的冗余来实现。
此对话可以追溯到 2025 年 3 月 4 日。
代码大小大幅缩小,从而大大提高了可维护性。 据我所知,大部分节省只是在消除 ECS 样板。
一切都感觉更紧凑、更直接。 更新迁移的焦虑消失了,虽然它被“我们必须完成这件事”的焦虑所取代,但随着进展的不断,这种焦虑很快就消散了。
切换后的生活
在过去的三个月中,我们一直在 Unity 中专门开发 Architect of Ruin。 这种转变明显改善了我们的日常开发。 迭代感觉更快,从而使想法更容易地流入游戏中。 我们还能够利用诸如 AStar Pathfinding Project 之类的生态系统工具。
一个尚未解决的问题,并且可能会给我们带来一些痛苦的领域是本地化。 在 Rust 中,Fluent 项目非常出色并且正是我们需要的——我尚未在 Unity 中找到可比较的解决方案。
我计划在未来的文章中讨论游戏在 Unity 中的具体实现以及移植过程。 今天这篇文章的目的只是解释导致我们目前处境的原因。
有几个结论很突出。
我没有在项目开始时公平地评估我的选择。 Rust 很棒,我喜欢它,但我没有给其他选择一个公平的机会。 特别是,我没有花时间更仔细地检查 Unreal 和 Unity 之间的差异。
有时你必须燃烧时间才能赢得时间。 我认为我们远远领先于我们坚持 Bevy 的情况。 我们在实现渲染功能的同时推进游戏玩法的敏捷性要高得多。
Rust 仍然是我非常喜欢的一种语言,Bevy 是一个令人兴奋的引擎,拥有一个很棒的社区——我对两者都非常尊重,并且很可能会再次将它们用于不同的项目。 然而,对于 Architect of Ruin 而言,对可访问的协作、快速的游戏玩法迭代以及利用稳定的生态系统的需求指向另一种选择。
这是一个艰难的决定,一个与我的直觉背道而驰的决定,但最终它使我们处于更有利的位置,可以实现我们对游戏的愿景。