一个人用Rust做游戏,从2018年做到游戏正式上线。他在Reddit的r/rust_gamedev发了AMA,回答了不少问题。这篇内容是他整理的AMA精选,做了一些编辑以便更清晰。
他做的游戏叫Way of Rhea,一款解谜游戏,如果你喜欢Braid、Talos Principle或者Portal,大概会合胃口。在转向Rust之前,他其实在写一个C++游戏引擎。
当时有个朋友一直劝他看看这门新语言,他没当回事。直到某天被CMake的某个蠢问题搞烦了——具体细节他已经记不清——决定歇几天,随便写点别的。他翻到Steve Klabnik写的Rust书,从头读到尾。
让他震惊的不是内存安全
真正让他改变想法的是别的东西:一门底层语言,居然自带构建系统、自带单元测试和文档生成、有代数数据类型、有非异常式的错误处理。知道有替代品之后,再继续用一个高摩擦的语言就很难说得过去了。
内存安全当然也不错,但作为一个做单机游戏的人,这对他来说不是首要考虑。从那以后,他开始积极推动在能不用C/C++的地方就不用。他同时活跃在Zig社区,还是Zig软件基金会的董事会成员。
他特意提到,网上总喜欢把这些努力当成互相竞争的球队来对待,但这是没抓住重点。对新手来说,好像必须把时间投给那个“正确”的语言,否则就全浪费了。为了逃避这种不适感,新程序员常常说服自己:我的语言或工具是完美无缺的,然后攻击任何批评它的人。
他认为更好的未来,是有多门高质量底层语言互相制衡。他鼓励新手少纠结语言选择,做游戏开发的人多看看手头的工具,挑适合当下需求的。
那Rust到底哪里让他难受
编译时间。除了编译时间之外——他补了这么一句。
他写游戏引擎,意味着有时候得做底层、抠比特的活。这类东西本来就难写对,但在Rust里难得多余。Unsafe Rust文档很差、很难用,而且你在网上求助的时候,别人表现得像你在写犯罪代码。
Rust的设计预期是你只做两种分配:堆分配和栈分配。你当然可以为代码库的不同部分实现自定义分配器,但那是在逆着纹理切。
还有一点让他挺沮丧:Rust不支持反射。
生态层面,他觉得有时候目标定错了。他记得2000年代初OOP当红的时候,如果你抱怨OOP没帮你达成目标,别人会告诉你你只是需要更OOP一点。Rust有时候也有点这个味道。
他的判断是:所有这些范式,只有在影响到一件事的时候才有意义——你软件的最终用户。
怎么避免掉进这个坑
他的办法是把事情做到底。如果你在做音频库,那就再做一个用它的音乐播放器。如果你在做游戏引擎,那就用它发布一款真正的游戏。
他强调这两个例子不是针对谁,只是随手想到的头两个。把事情做到底,你才能看到自己的决策如何影响最终用户,哪怕决策和用户之间隔着很多弯弯绕绕。这能让你保持诚实。
至于他实际用了哪些库,他说用了不少,但没有“承重”的。值得一提的有几个:
- 用gl拿OpenGL的函数指针,scopeguard在这里特别有用——RAII和OpenGL这类API不太搭,scopeguard相当于给Rust加了defer,让这类代码好写很多
- 窗口和键鼠输入是自己写的,卡住的时候参考SDL。他说这活挺烦,下一款游戏大概会用SDL3,但自己写学到了很多
- 用notify做热替换的文件变更通知,用各种STB头文件处理TTF解析这类事
- 用reqwest收集崩溃报告
有人问他解析脚本语言时有没有用解析库。他说自己不太喜欢解析库——写解析器一开始看着难,实际上随手写个递归下降解析器通常很容易。学校里会告诉你递归下降对常见文法不够强,理论上没错,但实践中大部分问题都能绕过去,或者干脆设计一个更简单的文法。
手柄支持踩的坑
他用了Steam Input做手柄支持。最终效果不错,但他以后不会再用了。
Steam Input是Valve的输入重映射功能。玩家可以玩一个可能没有手柄支持的游戏,把手柄重映射成键盘来获得想要的操作。这部分很好。
但Steam Input还有面向开发者的一面,提供了一些API。问题是这会把你绑死在Steam商店上,而且实现完全是坏的。层(Layers)从根本上就是坏的,在不同平台上表现不同,会以破坏游戏的方式出问题;实际输入行为有微妙的差异,似乎还和奇怪的Steam Overlay bug绑在一起;手柄重绑定UI也不总是显示出来,等等。
你得用Valve发明的一种领域特定语言来设置手柄绑定。这门语言文档很差——例子经常……
热门跟贴