信息化管理系统 | 数字孪生 · 智慧园区 · 数字大屏 | App · 微信 · 小程序 | 元宇宙 · 区块链 · 3D展厅 | 虚拟仿真系统 | 新零售电商

三年全职 Rust 游戏开发,真要放弃 Rust 吗?(8)

社交媒体对此文的一些评价

 

Rust 语言团队联合 Leader JoshTriplett[14] 对此回应

 

首先,非常感谢你花时间写下这篇帖子。离开Rust的人通常不会写下他们遇到的问题,这对我们来说是一个巨大的问题,因为这意味着我们大多只能听到那些问题不足以驱使他们离开的人的声音。非常感谢你,真的,感谢你愿意详细解释你遇到的问题。 我也非常同情和遗憾地听到你好像多次被告知问题是因为你没有正确使用Rust,或者因为使用了Arc或类似的东西而被羞辱,或者其他任何让你感到有罪的时候。人们不应该这样做,我很难过还有人这么做。 (相关地:你能否说明一下你在哪些场合遇到过这种居高临下的态度?我在我常去的Rust圈子里没有看到这种情况,但这显然是在发生,我经常看到有关这方面的抱怨,我希望这种情况不会发生。我们有时会尝试提供一些官方信息来阻止这种居高临下的态度,但也许我们还能做更多。) 我笔记本电脑上有一个“保持冷静并调用Clone”的贴纸,对于Arc及类似的东西也是如此,特别是当你试图优化原型设计速度和迭代速度时。快速修补让事情运行起来是没问题的。 你在这里提到的许多问题确实是Rust语言或生态系统库中常见模式的实际问题。例如,孤儿规则绝对是一个问题。它以多种方式影响生态系统的规模。这意味着如果你有一个提供特性的库A和一个提供类型的库B,要么A必须为B添加可选支持,要么B必须为A添加可选支持,或者有人必须通过新类型包装来解决这个问题。通常,较不受欢迎的库最终会为更受欢迎的库添加可选支持。这是为什么非常难以为serde编写替代品的一个原因:你必须让目前提供可选serde支持的每个库也为你的库提供可选支持。 在其他生态系统中,你要么在你的应用中添加快速且脏的支持,要么你编写(并可能发布)一个实现A和B一起使用的支持的A-B库。这在Rust中也应该是可能的。 对此有几个潜在的语言解决方案。最简单的,可能相对容易并且会帮助许多应用的,将是“一个类型只能有一个特性的实现”,如果有多于一个的实现,则给出编译器错误。 一个稍微复杂一点的规则是“允许相同的实现并视为单一实现”。这将与某种“独立派生”的机制结合使用时非常方便,这种机制将在任何使用它的地方生成相同的实现。 嘿,看,我们已经来到了这里另一个非常合理的抱怨,即宏系统与拥有某种反射的对比。我们应该提供足够的支持来实现一个独立的derive Trait for Type机制。它不必是完美的,对许多有用的目的来说已经足够好。这里的其他一些问题也可能有解决方案,我们值得尝试去弄清楚解决它们需要什么。 无论如何,再次感谢你写下这些。我打算带着我的语言帽子试图解决其中的一些问题,并鼓励其他人阅读这篇文章。

 

来自资深游戏开发的评论:

 

我作为一名拥有超过 12 年经验的游戏开发者的观点是,“长此以往”更为有利。作者希望在短期内优化他们的努力,而我希望在长期内优化我的成功。这是一个滑动的尺度,当然,但作者的观点与我的完全相反。 他们希望快速迭代和“设置并忘记”式的编码,以测试一时冲动的想法是否可行,就像原型制作一样。我希望确保我编写的代码尽可能少地出现错误,包括合理处理边界情况和错误条件。对于前者,像 Lua 这样的语言已经足够好了,许多游戏开发者出于这个原因使用它。对于后者,像 Rust 这样的语言已经足够好了,许多关注长期可维护性的工程师对它很感兴趣。 我曾经用 JavaScript、Python 和 Lua 编写过游戏,通常都是以同样的随意态度,随便拼凑一些东西,也许以后再回头看看。这对于立即满足某种需求来说非常好。但是,如果我需要负责修复那些代码中的错误,这就成了我生活中的灾难。如果你能让维护成为别人的问题,那就是完美的自私开发策略。(编辑:这是我关于自己的不必要的评论。它不是针对任何其他人的投射或指向。)回顾我以前项目中的所有混乱代码,它们实际上是无法触及的。Lua 和其他语言不适合修复错误而不破坏其他东西。 另一方面,我欣赏 Rust 的限制。这种语言让你很难自己给自己找麻烦。它强制你思考可变性。因为如果你不考虑它,那就是你刚刚引入的一个错误。这是 Rust 会禁止的错误。Rust 要求你处理边界情况。这样当出现错误或者做出错误假设时,你的代码不会盲目地继续执行。 在直接批评所写的内容时,我对于“现在只是解决问题,以后再修复”和“快速高效的代码”之间的认知失调感非常强烈。(编辑:认知失调是正常的!我也有罪。我喜欢动物,但我吃肉。某种程度的认知失调是无法避免的。)选择 Rust 是因为你希望游戏在相对较慢的硬件上运行得很快,但你期望“快速的代码”应该是免费的,你可以忽略像复制或克隆与指针之间的细节(包括静态、堆分配和引用计数指针变体)。 “更好的代码”和“更快的游戏”之间的连续是基于你的短期和长期目标来导航的。也许对你来说 Lua 是最合适的选择?也许是 JVM 或 CLR。也许是一个网页浏览器。在目前所有可用的选项中,对我来说是 Rust。垃圾回收不在考虑范围内。而且因为我有“长远/现在”的心态,我相信将来会有比 Rust 更适合我的东西。 另一个例子是他们特别指出一些问题是“self 造成的”,并且后来认为全局状态比使用 bevy 的 ECS 实现更容易。从某种角度来看,这可能是真的,但它忽略了全局状态不可避免地导致的所有错误。通常,可变别名错误(如 macroquad 中提到的不完全性)或更一般的问题(如 The Problem With Single-threaded Shared Mutability - In Pursuit of Laziness (manishearth.github.io)中所述)都是全局状态的问题。 但真正的问题是在“全局状态与 ECS 状态”之间划定界限是完全人为的(甚至是自我造成的)限制。一个游戏可以同时使用全局状态和 ECS,这不是一个选择的问题。这并不意味着它会变得容易。事实上,共享可变状态很困难,无论是本地还是全局,无论实现语言是什么。 他们绝对正确,Rust 不仅仅是一个“完成任务的工具”。它是一个能够以高性能正确完成任务的工具。还有很多其他语言可以“完成任务”,但这往往会以正确性、性能或两者兼而有之的代价为代价。 大多数 AAA 级游戏花费数年(甚至十年或更长时间)进行开发。如果这不是为了长期发展,我不知道还有什么是。 我可以理解这个观点对独立游戏来说是有效的,如果盲目地应用的话。但我相信大多数独立游戏开发者不只是每个月写一个游戏。这些游戏很大程度上都不会取得任何成功。而且,如果其中一个游戏确实取得了成功,那么他们就需要修复错误。支持他们无法访问的硬件配置。按照社区的期望提供新内容。等等。对我来说,这听起来很像长期维护。

如果您真的不关心安全性(尤其是如果您已经到了更喜欢使用允许多个可变借用的“不安全”语言的程度),在 Bevy 中您总是可以使用 get_unchecked 逃生通道。在“随便我想做什么”的游戏开发环境中,我认为这样做没有问题。而且,当在“安全”的代码库的更大背景下完成时,您可以同时兼顾两者。

作为一个长期从事游戏开发但是新手 Rust 开发者,这篇文章真是太棒了,肯定能为我节省一些时间和挫折。我仍然想尝试一下,但是知道了可以期待什么(你讨论的一些问题是我已经有的担忧的确认)。 我刚刚购买了《Unrelaxing Quacks》来表示感谢(也祝贺发布!)

这是一篇很好的文章。他在很多方面都是正确的。 我已经用 Rust 写了一个多年的元宇宙客户端。可以与 Second Life 和 Open Simulator 服务器一起使用。这是一些[视频](https://video.hardlimit.com/w/7usCE3v2RrWK6nuoSr4NHJ[15])。代码有大约 45,000 行的安全 Rust。 在 Rust 中从事严肃的 3D 游戏开发的人很少。有 Veloren,还有我的东西,可能还有其他一些。没有大型、受欢迎的游戏。我本以为现在应该有一些 AAA 级别的游戏是用 Rust 写的。但这还没有发生,而且可能不会发生,原因就是作者提到的。 他说得对,重构的痛苦和程序不同部分之间的互连困难是真实存在的。某些变化往往需要进行大量的工作。如果与服务器通信的客户端需要与 2D GUI 进行通信,它必须排队一个事件。 渲染情况几乎可以接受,但堆栈还没有完成和可靠。2D GUI 系统较弱,每个对话框需要太多的代码。 我倾向于同意“异步污染”问题。"异步"系统是为那些需要运行非常大的 Web 服务器,有大量客户端发送请求的人进行优化的。我一直在抵制它渗入那些实际上不需要它的领域。 我对编译时间的困扰比他少,因为元宇宙客户端没有内置的“游戏性”。元宇宙客户端更像是一个 3D 网络浏览器,而不是一个游戏。所有的对象和它们的行为都来自服务器。我可以在实时世界中编辑我的世界的一部分。如果某个东西的颜色、行为或模型需要改变,那不需要客户端重新编译。 使用 C#和 Unity 解决同一个问题的人进展得更快。 一个回复:我不太相信任何 AAA 级别的游戏会用 Rust 来编写(可以自行插入“因为 Rust 的游戏开发生态系统不成熟”或“因为 AAA 游戏开发越来越保守和风险规避”),但我很好奇你为什么会有这种想法。C++ 在 1985 年就可用了,直到千禧年之交,在 Quake 3 的推动下(受益于 C++98 的新特性),才在游戏开发中变得流行起来。

 

社区中其他人的看法,这里摘录一些代表性回复

 

我们使用 Rust 编写了一些流水线工具,包括 CLI 工具和在 DCC 中使用 Python 封装 Rust,文章的主要观点与我的经验相符。 当语言的限制与你所做的工作相符时,Rust 是很棒的选择,但它与游戏引擎中经常出现的实际安全但无法证明安全的模式不太兼容。 虽然我们可以绕过这些限制并生产出一个好的产品,在某些情况下甚至是一个“更好”的产品,但这些好处与我们的痛点不相符。我们没有赚更多的钱,没有更快的迭代,没有更快的交付,我们的维护成本也没有降低,我们只是用“困难的方式”做出了一些东西。

我在尝试过所有流行的引擎和许多不太流行的引擎之后,离发布我的第一个游戏比以往任何时候都更近了。主要原因是因为我可以使用 Rust 编程语言,并且喜欢这门语言,仅仅是与它一起工作就给了我继续前进的动力。我理解它的缺点和不足之处,但使用你喜欢的技术也是保持动力的重要因素。我使用 Bevy 引擎,虽然它是一个年轻的引擎,但非常有趣。

我对这里关于 ECS 的一些说法非常不同意,但我认为我理解作者的观点。 作者从“性能至上”的角度批评了 ECS,不幸的是,这往往被吹捧为采用 ECS 的原因(因此许多 ECS 库都是围绕这一点设计的,导致有时候 API 非常痛苦)。无论如何,作者指出他们经常在系统中写“if (player) ... else if (wall)”,我认为这真的凸显了对 ECS 的理解不足 - 在我看来,这很容易成为重构为多个系统的候选方案,但作者似乎固守着一个系统对应一个组件的思维方式。无论如何,我实际上很喜欢 Rust,但在很大程度上同意 - 我认为编写正确的代码和编写快速的代码是完全不同的事情,而 Rust 主要关注前者。 我是 ECS 的坚定支持者,但承认在 Rust 游戏开发社区中的交流并不多。 我个人的理念是组件应该非常细粒度地拆分,但系统不应该。我认为 ECS 对我来说最大的卖点就是系统将这些组件粘合在一起,形成逻辑上有意义的组合 - 你可能有一个 Player 组件、一个 Enemy 组件和一个 Health 组件,但是你可能会有 PlayerHealth 和 EnemyHealth 系统。(因为如果你只考虑有像...一个 PlayerSystem、一个 EnemySystem 和一个 HealthSystem 这样的东西,那我觉得你就失去了 ECS 的所有好处,还不如坚持 Unity 风格的“臃肿组件”,只是我猜你在 Rust 中不能真的这样做...)

我不得不说我同意。我希望我不同意,因为我真的很喜欢用 Rust 编程,但是(独立)游戏开发主要不是编程,而 Rust 真的妨碍了这项工作。

这归结为系统编程和游戏编程之间的差异。 大多数引擎使用 C++作为核心,并使用额外的“游戏脚本语言”。在 Unity 中,C#可以完成比 Lua 或 GDScript 更重的工作。 有些人希望 Rust 能够胜任“游戏脚本语言”的工作,但他们会感到失望。Rust 实际上是一种专注于大型项目的系统语言。 我认为没有任何一种语言可以适应所有人的需求。 我有很多相同的挫折,我不得不说我并没有发现 Rust 对于游戏开发是我想要的魔法子弹,但我因为其他原因而坚持使用它。成熟的 Rust 引擎需要与其他东西进行快速原型设计、游戏设计师工作的集成(填补 GDScript、虚幻蓝图或其他东西的角色)。 就个人而言,如果人们只想在底层引擎上进行大量工作,我会推荐他们只关注 Rust 用于游戏开发

也许与此无关,但作为一个外部人来看,读到这么多自信地说“Rust 绝对不是游戏开发的正确工具(暗示作者选择它是愚蠢的)”的评论,然后又有这么多自信地说“Rust 绝对是游戏开发的正确工具(暗示作者只是没有足够聪明地使用它)”的评论,真的让人感到不适。

这里提到的问题是否意味着 Rust 永远不适合游戏开发?我不太相信这一点,因为他们对 macroquad 等方面有积极的评价。可能更重要的是,我希望 Rust 在这方面变得更好,因为我完全可以想象在未来的某个时刻用我最喜欢的语言来实现一个小游戏,我会很喜欢这样做。

嗯,是的,这是 Rust 方面的选择。C++ 只是让你这样做,它可以工作,直到它不能工作为止。 我认为 ECS 已经被过度推崇了,Fyrox 比 bevy 更进一步,因为它们避免了架构上的冒险。在这一点上,你是百分之百正确的。 但是生命周期等等,那只是在等待崩溃的预防措施。有很多关于最后一刻的临时修补的故事,只为了让某个东西稳定运行到足够发货的程度。 借用检查器不会丢弃无效的代码,它会丢弃无法证明符合有效性的代码。这是一个微妙但重要的区别。

我认为 https://crates.io/crates/qcell 是一个非常棒且经常被忽视的解决方案,用于解决借用检查器的问题。其主要优势是能够拥有长期存在的对单元的引用,同时具有短期可变借用和潜在的许多相同借用者的单元。https://crates.io/crates/qcontext 是在此基础上构建的,以提供具有零成本内部可变性的静态内容。

关于 C++ 的说法:“它给了你一根绳子,让你用它上吊”是正确的,这是相反的立场。你可以随心所欲地使用它,但它允许开发者在某个时候有所顾虑并做任何他们想做的事情。接受风险,你必须知道何时可以或可以弯曲规则,甚至如何将风险降到最低。 在 Rust 中,如果编译器完全强制执行规则,你就没有办法这样做。所以有几次我觉得代码正确性被放在开发者判断之上,让你只有一个选择。消除了对正确或错误做什么的自由选择,只有一种方法来做事情。 我来自嵌入式世界,不能将硬件接口创建为单例让我非常恼火。而且,你需要绕过各种障碍来创建一个单例,比如使用库,这让我更加烦恼。

 

更多评论可以参考:

  • Reddit Rust 频道[16]
  • Hacker News[17]

 

后记

 

在我看来,这是一位来自「不追求代码质量,只要求快速迭代,验证功能的独立游戏开发者」对 Rust 的抱怨。但他的抱怨总归是合情合理的,确实说在了一些点子上,也许他确实是“踩在”了 Rust 语言的应用边界。

对于那些想要快速验证想法,且需要根据业务或用户/玩家反馈快速变化,且一次性的不需要维护的项目产品,也许用 Rust 确实是不合适的。这个世界并不是所有项目都需要严格的强制的安全和代码质量。

 

 

 

如有侵权请通知删除,谢谢!

本文转自;三年全职 Rust 游戏开发,真要放弃 Rust 吗?-腾讯云开发者社区-腾讯云 (tencent.com)