Dominic enjoys writing C in his free time because it provides a more hands-on, detailed programming experience compared to high-level languages like JavaScript. C forces him to think about all the details, which he finds enjoyable and challenging.
The source code was described as a mess, with global variables scattered across files, ad-hoc additions for different platforms, and a lack of abstraction. However, despite its poor structure, the game was playable and never crashed on the PlayStation.
The primary difference was the frame rate. NTSC ran at 60 Hz, while PAL ran at 50 Hz. This required adjustments in the game's physics and rendering to ensure smooth gameplay across different regions.
Dominic implemented a modern rendering approach that abstracts the renderer, allowing for easier porting to different platforms. He replaced the PlayStation's ordering table with a more modern triangle function, making the rendering process more efficient.
Dominic used a bump allocator, which allocates memory linearly and resets the pointer when a scene or level ends. This approach simplifies memory management by avoiding the need to free individual objects, making it ideal for games with distinct scenes like Wipeout.
The main challenge was ensuring the game could run within the constraints of the web, where infinite loops are not allowed. Dominic had to refactor the code to use callbacks like requestAnimationFrame, allowing the browser to regain control between frames.
Dominic wants to fix a physics bug that occurs at high frame rates, add graphical effects like lighting on ships, and make the collision response more forgiving. He also dreams of a polished remaster if Sony were to commission it.
Dominic is currently rewriting his old JavaScript game engine, Impact.js, in C. He has already ported one of his games, Biolab Disaster, to run on the new C engine, which can compile to WebAssembly.
<raw_text>0 Wipeout是一款未来主义赛车游戏,最初于1995年在PlayStation上发布。该游戏融合了快速的游戏玩法、引人注目的艺术风格和授权的电子音乐。它是一个文化现象,也是控制台游戏中3D图形的早期展示。多米尼克·萨布列夫斯基是一名工程师、游戏开发者和黑客,他发布了诸如Voidcall、QuakeVR和Q1K3等项目,后者是用JavaScript编写的13千字节版本的Quake。
2022年,Wipeout的源代码版本被泄露,多米尼克创建了一个几乎完整的游戏重写版本,可以编译到Windows、Linux、macOS和Wasm。
多米尼克加入播客讨论这个项目。乔·纳什是一名开发者、教育者和获奖的社区建设者,曾在GitHub、Twilio、Unity和PayPal等公司工作。乔通过创建Garry's Mod的模组和运行服务器开始了他的软件开发之路,游戏开发仍然是他体验和探索新技术和概念的最爱方式。
欢迎多米尼克,你好吗?我很好,谢谢。太好了,非常感谢你今天加入我们。那么,首先,我们在这里讨论一款经典PlayStation游戏的重写,但我想知道你的游戏开发之旅是什么样的?你是如何走到这一步的?哦,这是一个相当漫长的旅程。我认为我写的第一款游戏是在我父母的486电脑上用Quick Basic编写的。
但后来开发游戏变得非常困难,因为为Windows开发游戏很复杂,需要设置编译器等等。我有一段时间没有做这个。但后来,我完全忽视了Flash。但我记得当史蒂夫·乔布斯宣布iPhone上不会有Flash时,我想,也许有机会用浏览器自带的东西来做,比如HTML5。
我开发了这个HTML5游戏引擎,并且取得了相当大的成功。然后我用这个游戏引擎做了一些休闲游戏和一些原创游戏。是的,我一直对复古游戏也很感兴趣。这太棒了。是的。你的游戏引擎是Impact,对吗?没错。我对它很熟悉。听到它的来源和时机真好,因为我一直认为它是一个非常早期的HTML5游戏引擎。听到这确实是明确的目的,真是太酷了。是的。
那么,接下来,我想知道你是如何参与Wipeout的?Wipeout是你早期生活中玩过的游戏吗?我看到你做了很多与Wipeout相关的项目,直到这个完整的重写。它一直是你感兴趣的东西吗?是的,有点。可以说这是我成长过程中最具影响力的游戏之一。
我们家有一台超级任天堂,PlayStation发布时我们看到了广告,感觉太棒了。但它太贵了。所以我们让父亲去当地的视频租赁店租了一台PlayStation几天。他带回了PlayStation和Wipeout。还有可能是另一款游戏,我记不清了。但Wipeout绝对在其中,我们整个周末都在玩,真的很好。我们从未见过这样的东西。几年后,我
在一家当地商店看到他们以大约10欧元或10德国马克的价格出售Wipeout的PC版。所以我买下了它,并在我的PC上玩了很多。但我注意到它并没有PlayStation原版那么好。不过,我还是在CD上翻找,看到有PCX图形,我试着把我的名字放在游戏中的广告牌上。结果从来没有成功。我不知道,最后一切都变得混乱。
但我一直对破解Wipeout感兴趣。太棒了。对于那些不知情的人,你能告诉我们这款游戏是什么以及为什么它如此吸引你吗?我认为它是1995年原版PlayStation的发售标题。它是由一家英国开发商开发的...
他们有一种非常独特的图形风格。有一位专业的图形设计师为游戏中的所有标志和菜单艺术品制作了所有内容。但这款游戏真的很重要,因为...
如果你知道来自超级任天堂的F-Zero,那就像是它,但更成熟的版本。它是完全3D的。它有多边形,还有这种舞曲音乐,给游戏带来了更严肃的,可能不是严肃的,但更成熟的氛围。所以,是的,我想我遗漏了最重要的事情。这是一款赛车游戏,一款未来主义赛车游戏,你在这些悬浮飞船中飞行,这些飞船通过磁铁或其他方式附着在轨道上。
是的,我的意思是,你说你遗漏了最重要的部分。你提到了音乐。实际上我没有。当Wipeout发布时我还很年轻,我直到Noclip纪录片才意识到这款游戏是多么依赖音乐,而不是想要创造任何特定的游戏玩法,这对它来说是多么重要。是的,你提到F-Zero也很有趣。再次反思...
F-Zero和Wipeout,直到今天的聊天。我只是想到了快速反重力赛车的类型确实已经消亡了,对吧?这些游戏没有真正的同类。但在这个
奇特的赛车游戏阵容中确实有几款标题。实际上,我不确定我是否记对了名字,我认为是BeamNG。好的。那是Wipeout的现代诠释。它在Steam上。很有趣。我得查一下名字,但它相当不错。太好了。好的,知道了。但似乎没有大型工作室再次涉足这个领域。是的,绝对如此。好的。那么,关于这个项目。
你已经重写了Wipeout,根据我的理解,是基于几年前泄露的一些源代码。你能告诉我们吗?这个项目是如何开始的?实际上,它开始得比那早一点。我对Wipeout的数据格式进行了逆向工程。当我年轻时,我试着把我的名字放入图形中,但没有成功。后来,随着经验的增加,我在2016年试图弄清楚Wipeout的所有数据格式。
我构建了一个JavaScript渲染器,可以加载所有赛道和飞船,并通过WebGL在浏览器中显示。因此,我对数据格式已经有了一定的了解。我对这个Wipeout的事情很感兴趣。然后源代码泄露发生了。我不认为有任何信息说明它究竟来自哪里,但它就在一个网站上出现了。它没有附带任何许可证。
所以这算是一个法律灰色地带,我想。虽然我无法直接进入它,但我想花了一年时间才找到时间和动力去真正深入研究。是的,我下载了包含所有代码的zip文件,开始挖掘。太棒了。是的,法律灰色地带,我认为这是一个非常有趣的地方。简单提一下。
你在你的博客文章中提到,顺便说一句,写得很棒。大家请查看这篇博客文章。确保将其放入节目说明中。你在重写的博客文章中提到,其他人也曾尝试过这段源代码,其他人也对其做过一些事情。你追求重写的部分原因是,您知道,公平使用的考虑。对于那些不熟悉源代码许可的乐趣世界的人,你能简要介绍一下吗?以及为什么在这种情况下,重写是探索源代码的有趣方式?
嗯,这有点开玩笑。我想即使是这个重写,我仍然处于灰色地带。
所以是的,源代码通常,如果你编写一个程序,它通常是受版权保护的。如果有人窃取你的源代码,你可以起诉他们侵犯版权。然后你可以附加许可证到你的源代码上,说,嘿,如果你使用源代码并修改它,你也必须发布修改后的源代码。这是GPL许可证。然后还有更宽松的许可证,比如MIT许可证,允许你做任何你想做的事情。而我在博客文章中提出的论点是...
好的,它是基于原始源代码,但这是一个完整的重写。没有一行代码是原来的,所有内容都是不同的。所以我认为你仍然可以在很多地方看到与原始源代码的相似之处。但是的,你可以提出一个论点,它是一个原创作品。是的。再说一次,我想这算是灰色地带。是的,我见过一些...
疯狂的事情,确保它是这种事情的干净房间实现,比如有人阅读代码描述算法,然后把它交给从未见过代码的人,这种事情,所以这真的是一个有趣的项目,但这似乎一定是一个非常有趣的项目,试图以你所说的原创和不同的方式重建这个东西,所以考虑到这一点,我想问你的第一个问题是,你知道
你知道,正如我们在你的介绍中所涵盖的那样,你做了很多,我想我会说像低级别的JavaScript编程。你做了WebGL,你写了一个框架,但,从我看到的你的背景来看,你是一名Web开发者,一名Web工程师。你为什么决定用C重写它,而不是用JavaScript或Impact呢?好吧,在我的日常工作中,我必须用很多非常高级的语言编写代码。所以JavaScript,我做了很多PHP、Kotlin和Swift用于iPhone。
所有这些非常简单的语言,通常很容易使用,但它们掩盖了真正发生的事情。而且,近年来,我发现用C编写代码在我的空闲时间里很有趣,因为它就像
你进入了一种禅的状态,我想。所以这非常,你必须考虑所有细节,拼凑在一起很有趣。这就像解决一个大难题,它给你带来了所有的挑战,而这些挑战在更高级的语言中是没有的,你只需将一些对象组合在一起,看看会发生什么。而在C中,你真的必须小心你所做的事情。这对我来说就是吸引力。所以我真的喜欢写C,即使这不是经济上明智的选择。
是的,这太棒了。是的,我喜欢这种方法,我不想说历史,但你知道,像早期的编程语言,具有更多尖锐边缘的语言,作为你在工作中必须使用的现代语言的有趣替代品。那么重写的状态如何?我玩过,它是可玩的,似乎很棒。你怎么看?
嗯,现在很难找到更多的动力去做它,既然它已经发布了。你知道,一旦你发布了某个东西并获得了所有的赞誉,你就会停止大量工作。它几乎完成了。仍然有一些小细节缺失。当然,还有一大堆
可以改进的地方,比如对原始游戏的改进。原始游戏的学习曲线非常陡峭,因为碰撞反应实在是太不宽容了。你撞到墙壁就会停下来。
而新游戏修复了这个问题,Wipeout系列的新游戏也可以将其移植到这个版本中。然后只是像图形效果,比如飞船,例如,未被照亮。如果你进入一个黑暗的隧道,这不会反映在飞船上。当你撞到墙壁时,火花飞溅或类似的东西。但,是的,它是可玩的,已经完成。
可能还有一些物理错误,因为我重写了所有的物理代码,可能在某些地方并不完全独立于帧率。但如果你以60 FPS玩,它应该没问题。是的,所以我会说基本上完成了。但一如既往,最后的10%或90%的工作。所以谁知道索尼是否真的会接触我,要求我做一个完整的移植并进行打磨。这可能仍然会是更多的工作。
完美。是的,我真的很喜欢你提到的回溯机制。我们稍后会再谈这个,因为我有一堆关于你对这个项目的历史档案目的的看法的问题。但首先,我想深入了解重写本身以及它是如何产生的。我想开始的最佳地点是你发现的泄露源代码的状态。我认为从你的博客文章来看,你对它并不太满意。我相信你用了“可怕”这个词,我想这有点开玩笑。
但你能告诉我们一下这款游戏发布时源代码的状态吗?是的,我想我在博客文章中说得很严厉。你可以从两个方面来看待它。一种看法是仅仅看到源代码,看到它是一团糟。我不知道,声明在一个文件中的全局变量在另一个文件中被使用,这一切都没有意义。它到处都是。然后你可以从另一个角度来看,
说,嘿,这是一款非常酷的游戏,很多人都喜欢它,它可以运行,并且在PSX上从未崩溃。它是完全可玩的。因此,最终有一个可用的游戏也许证明了手段的合理性。所以也许源代码都是糟糕的,但游戏是可玩的。是的。是的,我想它是如何结合在一起的,如果你真的深入研究源代码,试图弄清楚什么是先来的,你
你可以看到,也许赛道渲染、赛车物理是最早实现的。然后其他所有东西都堆在上面。最终使源代码变得糟糕的原因是,他们还在上面堆叠了PC版本。并且有很多if语句,比如,如果这是PC版,就这样做。如果是PlayStation版,就那样做。即使对于PlayStation版本,他们也有很多针对不同类型控制器的if语句。
这一切都是非常临时的,全部堆叠在之前的版本上。然后,正如我提到的,这款游戏是在英国开发的。因此,他们首先构建了PAL版本,然后不得不制作NTSC版本。由于游戏不是帧率独立的,它有一个固定的滴答率。
因此,它每帧都以相同的时间步进推进。他们不得不在很多地方进行更改。再次,他们用一堆if语句来处理这个问题。就像,嘿,如果这是NTSC,就这样处理时间步进。
再加上PC版本在上面,然后出现了一个针对ATI Rage的GPU加速版本。所有这些都混合在一起,成为泄露的同一源代码。我想,如果我得到了干净的PAL版本PlayStation源代码,整个重写会容易得多。是的,但我想看到这种游戏历史的考古层次一定很有趣,因为它是如何从平台和地区移植和移动的。
你描述的功能缺失也很有趣。我认为赛道,是否是顶点光照渲染器或其他东西在版本之间消失了?
是的,原版PlayStation版本有纹理赛道,且也进行了顶点光照。因此场景的每个顶点都可以有一个独特的颜色,他们将所有这些烘焙到模型中。我不知道他们是怎么做到的。我想他们是手工绘制的。我不认为他们有像现代游戏那样的光照计算。你知道,对于现代游戏,你有这个光照贴图步骤或其他东西。我不认为他们有这个。而所有这些顶点颜色在
PC DOS版本中都消失了。实际上,有一位参与此项目的程序员在Twitter上联系了我,说,嘿,是的,我们尽力了,但这实在太慢了。我们无法在纹理赛道上进行顶点滑动,以使其运行得足够快。然后我想ATI Rage版本只是一个促销项目,确实是垃圾的巅峰。你可以看到它只是快速拼凑在一起,没有太多的关心。
有趣的是,每次都是不同的程序员团队处理这些版本。因此,PC DOS版本由与PlayStation版本不同的人处理。而LTIRH版本则由不同的人处理。他们都必须理解之前的内容。因此,我想他们的工作也很艰难。
是的,尤其是那个ATI Rage版本让我特别着迷。只是经济学方面的事情。我记得在过去,当你得到一台新电脑或新的电脑组件时,它会附带一些免费的游戏CD或其他东西。我想这就是这种设置的想法。因此,在这种条件下移植一款游戏的想法,像是给这个移植公司的预算可能非常有限。因此,这种情况是可以理解的。是的。
但好吧,回到这些层次上,你刚才提到的,以及在博客文章中提到的NTSC和PAL之间的区别。这是我想我多年来没有考虑过的事情,基本上自从...但像是长大后,我总是对这些地区存在有一种模糊的理解,因为有时你会得到来自错误地区的PlayStation游戏,但我从未真正研究过技术差异。NTSC和PAL之间的实际区别是什么,以及这在移植游戏时如何体现?
Wipeout的主要区别是NTSC是60赫兹,而PAL是50赫兹。是的,这对所有之前的平台都很复杂,比如超级任天堂游戏也有同样的问题。开发者通常有点懒惰,超级任天堂版本运行得稍微慢一点。
而我们在欧洲的任何人都不知道,所以没有人关心。但是的,主要区别归结为帧率。如今,每个人至少都有一个60赫兹的显示器,甚至是可变刷新率。
现在,游戏如何处理这个问题是,你有一个可变的时间步进。至少这是我的理解。我不在AAA游戏开发领域。但你只需测量自上一个帧渲染以来经过了多少时间。因此,对于60赫兹,通常是16毫秒,如果你达到了帧率。然后你只需将所有物理计算乘以这个时间步进。
因此,如果你将速度乘以滴答率,你将获得一个平滑的物理集成,在各种不同的帧率上大致相同。但在过去,没有人这样做,因为你知道,如果你为超级任天堂或PlayStation购买了一款游戏,你知道你将达到30 FPS或60 FPS。你知道你的时间步进将是什么。这使得开发变得更加容易,因为你从来不需要处理可变时间步进和时间。
我想你在这些旧控制台上也受到限制。因此,对于每个你想移动的对象乘以时间步进是相当繁琐的。我想在PlayStation上可以接受,但在之前的控制台上就不太行。令人着迷。是的,我确实考虑过帧率独立性的成本。这是一个很好的观点,实际上。尤其是额外的摩尔。是的,这一定会累积。
太棒了。那么,谈到旧控制台,以及成本和硬件。你知道,任何游戏,即使是现代游戏,都是非常依赖于它们构建的架构的。这对于控制台游戏尤其如此,尤其是Wipeout,
你稍微提到了Wipeout的起源,但据我了解,它不仅是发售标题,而且开发者或发行商在使PlayStation成为可用的开发平台方面也非常参与。因此,这一切都交织在一起,形成了一个大混乱。
在源代码中有没有有趣的地方,你真的能看到PlayStation架构的体现?有没有一些东西,简直就是,没有人会以这种方式为任何其他控制台或环境编写,这纯粹是PlayStation的遗物。哦,是的,当然。在渲染代码中,尤其是非常特定于PlayStation控制台环境。
通常,如今当你编写游戏时,你有你的游戏对象,你有你的渲染器,像是一个抽象的东西在一旁。因此,你可以随时切换你的渲染代码。比如你想从OpenGL切换到Vulkan,你只需在代码中替换一个模块,它大致可以正常工作,而无需触及任何内容。
源代码在游戏中到处都是。但在那时,你没有这种自由。所有这些抽象显然会消耗一些计算时间,而你正在尽可能接近底层编写代码。你试图立即处理这些事情。因此,Wipeout源代码中的所有渲染代码都是非常特定于PlayStation的,且非常临时。
没有渲染器模块,只是接受所有三角形,然后试图找出如何最好地将其提交给硬件或类似的东西。因此,是的,这可能是最接近PlayStation的部分。但还有各种不同的事情,比如控制器。例如,控制器输入也没有抽象,无论你连接的是PC键盘、游戏控制器还是赛车方向盘。所有这些控制器也在源代码中
像是用if语句处理的,比如,嘿,如果这是PC版本并且连接了鼠标,那么现在检查鼠标坐标。并不是像抽象的方式,你有一个输入模块说,嘿,当鼠标向左移动时,将输入传递给游戏向左移动。因此,是的,非常临时。但我想这也是因为这款游戏如此成功,他们没有预料到需要将其移植到这么多不同的平台,因此最终一切都堆在一起。是的,渲染器听起来...
真是噩梦。我发现这是一个特别有趣的话题。实际上,当我进行重写时,我研究了PlayStation如何在屏幕上生成多边形。对我来说最有趣的是,PlayStation没有Z缓冲区。因此,如果你进行3D渲染并向GPU提交两个三角形进行渲染并进行光栅化,CPU会借助Z缓冲区来确定哪个三角形在另一个三角形前面。
因此,如果你绘制一个三角形,它会被绘制到颜色缓冲区,并且你有Z缓冲区,它只存储深度值,比如到相机的距离。如果你绘制另一个三角形,它会与存储的深度值进行比较。如果你有一个深度值比你想要写入的深度值更接近相机,那么你
你的对象在这个像素上被之前绘制的对象遮挡。因此,GPU不会绘制它。作为程序员,你不必太关心这个。你只需设置你的深度缓冲区,GPU会处理它。但再说一次,PlayStation没有这个。你必须按照它们在屏幕上应该出现的顺序绘制所有多边形。因此,最远的三角形需要首先绘制。
然后越靠近相机,越往上堆叠。与PlayStation一起提供的开发库有一些很酷的函数,使这一切变得更加人性化。他们有一个排序表,你可以设置并说,我想要8000个不同的深度值。对于你想绘制的每个多边形,你计算它离相机的距离。然后你将其放入排序表中。
如果你为所有多边形这样做,最终你就会得到正确的排序,因为它是从后到前绘制的。因此,你将所有内容放入排序表中,就像一个大数组。实现细节比这更有趣。它是从后到前绘制的。
这有趣的影响是,你会有一些视觉伪影。例如,假设你在屏幕上有三个三角形,其中一个三角形覆盖了另一个三角形,像是层叠在一起,而没有一个三角形在所有其他三角形的前面。因此,每个三角形都被其他三角形覆盖。你无法在PlayStation上准确绘制这个,因为你只能在一个深度值上绘制三角形,而...
你可以通过现代GPU的Z缓冲区来解决这个问题。
<raw_text>0 所以如果你想在PlayStation上准确地做到这一点,你需要对多边形进行细分,基本上就是这样。所以这变得非常复杂。你可以看到很多这些问题在PlayStation游戏中出现。比如如果你仔细观察,玩一款PlayStation游戏,你会看到一些多边形在不该出现的地方露出来。但大多数开发者都做得很好,隐藏了这些问题,或者分辨率太低,以至于你没有注意到或不在乎。对吧。
是的,我也立刻在想,8000个深度值够吗?但我想对于那个时代的大多数游戏来说,肯定是够的。而且我想这并没有对你如何定义深度有偏见,对吧?这取决于你游戏的规模。
MARTIN SPLITT:是的,没错。作为开发者,你负责将其放置在正确的位置。所以你可以说,哦,它离相机五公里远,所以我会把它放在6000的位置之类的。规模由你决定。MARK MANDEL:有趣。
好的,我想开始问一个我们之前提到的问题,当你谈论回溯机制时,你的目标是多大程度上重现这段代码,使其准确反映游戏的工作方式,或者说代码的工作方式与游戏机制之间的关系?你只是想让游戏尽可能好,还是有一些努力去保留源代码的精神,如果这样说有意义的话?
嗯,我一开始只是查看源代码,我想做的就是让它再次运行,让它在现代计算机上编译并运行。但我越是深入阅读,越是改变,越是注意到代码质量有很多改进的空间。出于某种原因,我真的很喜欢重构代码,我想大多数程序员都是如此。于是事情就这样发展下去,我改变了这个,现在我需要改变那个。
所以我并不是一开始就打算进行全面重写,但最后就是这样发生了,因为我觉得这是处理这个问题的最佳方式,让游戏在未来的许多年里继续存在。因为如果你有这段混乱的原始源代码,让它在每个平台上运行,每一个新平台的出现都是非常耗时且困难的。但如果你有一个干净的起点,某种程度上
和一个干净的源代码,在这种情况下,甚至将渲染器抽象掉。这样你可以插入另一个渲染模块,让它在不同的平台上运行。这也使得保持游戏的生命力变得容易得多。所以,是的,这基本上都是偶然发生的。
太棒了。是的,你提到了一个新的渲染模块。所以我下一个问题是,你如何处理PlayStation的这些架构奇特之处?我想你已经将其更改为更现代的渲染方法。这准确吗?是的。所以我做的第一件事是尝试让它编译并运行,这一开始就非常困难。
嗯,尝试让它编译的第一步已经非常具有挑战性,因为它依赖于许多不同的东西,而这些东西已经不存在了。例如,对于ATI Rage版本,所有我没有的各种Windows头文件或已经不存在的GPU。对于PlayStation版本也是同样的情况。我没有PlayStation来为其编译,并且缺少PlayStation库。
所以我只是删除了所有不真正必要的启动游戏的东西。比如注释掉所有渲染多边形的地方,注释掉所有检查控制器输入的地方,注释掉发生PlayStation特定事件的地方。然后我终于让它编译成功。然后我开始慢慢地将东西重新添加回来。再说一次,PlayStation版本使用了这个排序表,并在
协处理器上转换多边形,但DOS版本是在CPU上进行转换的,并且他们在那里有所有的例程来做到这一点。但他们仍然使用这个排序表,因为它在源代码中根深蒂固。
我也开始使用这个排序表。我只是,你知道,所有在每个CPU上运行的多边形转换,你可以在现代计算机上运行。然后最后一步,当这些多边形被交给GPU时,我交换了它,像是如何通过排序表传递并显示出来。是的,我最初得到的是这个平面着色的Wipeout版本,它只是让屏幕上显示一些运行Wipeout的东西。
从那时起,我开始进一步清理,比如去掉这个排序表,拥有一个合适的三角形函数,并使用它,而不是到处将东西放入排序表。是的,所以...
一步一步,非常非常缓慢的进展,有时非常繁琐,因为你也试图去掉一些在里面的东西并重写它。然后你工作了五六个小时,却无法在此期间编译以查看它是否有效,因为你改变了太多东西以便于此。
所以是的,往往相当冒险,但也很有趣。我意识到我没有问的一个问题是,你有没有一个想法?抱歉,这是一个关于数字的问题,随口而出。原始源代码泄露的行数有多大?而重写后的行数又有多大?我想我实际上在我的博客文章中有这些统计数据。哦,真的吗?是的。
但你计算的方式有点复杂,因为源代码泄露中有三个版本的游戏,但这些版本都不能独立工作。如果你想编译PC DOS版本,它还会拉入一些PlayStation版本的源代码。所以我不知道如何准确计算这一点。再说一次,能够获得原始PlayStation版本的源代码会很好,因为那样你就会有更准确的数字。
好的,我找到了。所以泄露的代码有40000行,而你最终得到的约为8000行。是的。再说一次,很多东西你会看到相同的if语句到处都是。比如说,如果是这个控制器,就这样做。如果是另一个控制器,就那样做。如果你只是将其抽象为一个输入模块,你就可以消除很多重复和很多源代码,很多源行。太棒了。
太棒了。所以你提到过,你抽象了一些东西,以便可以使用不同的渲染器、不同的后端等。他们在STL2和Sokol上都运行游戏,对吗?是的,没错。我一开始使用STL2。我在Linux上做这个,我这里只有OpenGL,所以我先尝试让它运行。
嗯,有两件事你可以替换。重写的源代码是以一种你可以替换我称之为平台的方式构建的,那就是SDL或所谓的应用程序。然后你可以替换渲染器。目前有官方的OpenGL版本,我有一个线框软件渲染版本。还有其他人为macOS编写了Metal的移植。
我试着记住我是否看到其他东西。也许有人又将其移植到一些更新的控制台。我不确定。所以是的,我一开始使用STL平台,因为我知道我想要有不同平台的可能性,因此添加所谓的后端相当简单。平台需要实现的函数非常少,比如设置窗口、声音输出等等。
而Sokol实际上非常酷。libstl是一个大型库,你可以获得头文件以便编译你的代码。但你还需要链接到你的二进制文件,或者只是动态链接。我想它大约是三兆字节左右。而另一方面,Sokol则要求你包含整个源代码。
它全部在一个文件中,你只需将其包含在你的C源代码中,它就能正常工作。当我在桌面上让这个工作时,我实际上感到非常惊讶,我可以打开一个窗口并使用Sokol玩游戏,输入也能正常工作等等。
我只需将编译器从通常的GCC切换到EMScripten,它就编译了Web版本,并且正常工作。我可以玩游戏,声音输出正常,图形通过WebGL正常工作。这真是令人惊讶。我原本预期在网络上会遇到更多的麻烦。
是的,我在你的项目之前没有听说过它。但是的,这真的很巧妙。这引出了我的下一个问题,你的目标是Wasm,你的目标是网络平台。我想你刚刚回答了这个问题,即你不需要做任何特别的事情,因为Sokol为你处理了。但在重写过程中,有没有什么需要考虑的事情,因为你是针对Wasm的?除了我想要针对不同的平台之外,实际上没有,因为你知道,我无论如何都在使用OpenGL,而我知道WebGL与之非常接近。
你有这个GL ES2。我不需要任何花哨的着色器代码。就像,我没有任何阴影计算或其他东西。它只是带有颜色的纹理三角形,基本上就是这样。WebGL可以很好地做到这一点。因此在这方面,我没有
不需要考虑任何变化。最后,我确实需要为WASM版本做一点工作,以使其在移动设备上正常工作,尤其是,你知道,我需要在屏幕上有一些按钮,然后将其转发到C代码。这有点工作。而且这并不是很...
并不是很有趣,因为这些屏幕上的按钮反应不太灵敏,位置也不对。更像是一个概念验证。它在移动设备上工作,渲染在你的iPhone上也很好。我很惊讶它能在...我无法想象在iPhone上玩Wipeout。按钮挡住了视线,我总觉得这对于那个小屏幕来说太快了。所以我想稍微回顾一下,回到重写的内容,你在你的...
博客文章中提到了一些有趣的部分,你提到甚至不一定查看原始源代码,因为它太像意大利面了,而是查看游戏播放的视频或在模拟器上玩游戏。你能谈谈这些领域以及你为什么采取这种方法吗?嗯,其中一个领域是声音代码。同样,非常特定于PlayStation上发生的事情。
这实际上是相当抽象的。因此在游戏中,有调用在特定位置播放声音的代码。我知道游戏想要做什么。
我只是想,好的,与其深入挖掘所有这些声音代码,我不如重新实现,因为我知道他们想要实现什么。音乐在播放,声音在不同的位置播放,我知道它们被调用的地方。我认为这是一个相当不错的决定,因为再说一次,
原始代码是如此特定于PlayStation,以至于要弄清楚所有内容需要花费大量时间。重新编写它是更快的方法。不过,有一件事缺失。原始PlayStation有这种大厅效果。比如如果你进入隧道,你可以听到引擎声音的混响,以及正在播放的音乐的混响。而这在重写中仍然缺失。
有趣的是,正在播放的音乐的混响也是一个有趣的点,因为这意味着它来自飞船,而不仅仅是背景音乐,我以前从未注意到过。你在播客中说的一件事让我很感兴趣,因为我之前没有听过这个术语。所以在某个时候,你描述原始代码遵循的是“你调用平台”的方法,而你想要的是“平台调用你”的方法。你是什么意思?
所以通常你编写游戏的方式是这样的:你启动程序,加载图形和声音文件以及运行游戏所需的一切。然后你进入一个无限循环,永远运行。在这个无限循环中,你检查是否按下了任何按钮,推进你的物理计算。因此推进世界状态,然后将所有内容放到屏幕上。然后进入下一个迭代。
所以你调用平台,你自己调用所有需要做的事情。因此你在自己的代码中等待下一个,直到渲染器完成,然后将所有内容提交给渲染器。而在网络和WebAssembly中,你不能这样做,因为你基本上会崩溃整个页面或使页面停滞。你不能进入无限循环。你必须将控制权交还给浏览器。
所以,比如说,如果你在JavaScript中写一个无限循环,浏览器在15秒后会说,嘿,这个页面不再响应了。在此期间你无法与这个页面交互。因此所有这些,因为JavaScript是单线程的,如果你不使用Web Worker或其他东西,就没有任何东西在后台运行。
如果你想渲染一个游戏并在屏幕上放置一些东西,你进行物理计算,拉取输入,然后将其放到屏幕上。然后你将控制权交还给浏览器,等待你的代码再次被调用。因此你所做的就是设置一个间隔或请求动画帧,如今,你只需说,我希望每16毫秒调用这个回调。
在这些回调中,你做所有需要做的事情,然后返回。这是你在网络上唯一能做到的方式。有趣的是,原始Wipeout代码不仅仅有一个无限循环。即使你干净地实现游戏,你也会有一个处理各种不同场景的循环。
所以你有你的无限循环来拉取输入。如果你在菜单场景中,你显示菜单并处理菜单按钮等等。如果你在游戏中,你推进物理。你有这个最外层的无限循环。
而Wipeout就是这样。它在标题屏幕上有这个无限循环。所以它只是显示一张图像并说:“按开始。”并且有一个无限循环只是闪烁“按开始”按钮。一旦你按下开始,它就跳转到代码的另一部分,那里有另一个无限循环。哦,我明白了。你有这个菜单的无限循环。如果你进入游戏,你在比赛函数中还有另一个无限循环。
所以有很多不同的无限循环需要解开。是的,弄清楚所有这些以及如何在这些循环之间切换以及如何维护所有状态和所有加载的内容是相当麻烦的。比如说,你还需要哪些类型的图形?哪些图形可以丢弃?因为它们来自不同的赛道或其他东西。这是相当多的工作。是的,这听起来像是一场噩梦。但你刚刚提醒我,谈到你保留哪些图形,你在重写中的内存管理方法很有趣,对吧?你不再分配内存,而是
说:“我将拥有这么多内存,这就是我永远需要的。”你能谈谈这个吗?是的,这实际上是许多游戏开发中的一种标准方法,你有一个增量分配器。所以这是一个你首先分配的大内存区域。
然后你说,我从索引零开始。如果我想要,我不知道,1000字节的内存,你只需将这个索引推进到1000,并返回在零处的内存地址。然后下次你想要20字节时,你将索引推进20,并返回之前的位置。因此这就像是一个线性内存,随着时间的推移不断增长。
一切都在之前分配的东西之上增长。如果你不再需要你的内存,你只需将索引重置为零,你不必释放任何东西。你只需知道我在这里分配的所有内存现在都是免费的。如今,许多游戏都这样做,比如他们有这个增量分配器
每帧。因此在帧开始时,他们从零开始,游戏中的某些东西需要分配一些内存来进行一些计算或其他东西。分配器不断增长。在帧结束时,它再次重置为零。因此作为开发者,作为游戏开发者,你永远不必关心
内存分配。只要你的缓冲区总的来说足够大以处理一帧,你就不必再释放任何东西,因为它在帧结束时自动释放。你可以使用这个概念
你可以进一步扩展,只需说:“嘿,我不仅有一个这样的增量分配器,还有多个。”所以一个是每帧的,另一个是我不知道的,每个关卡。因此每次加载一个关卡时,它都会重置。是的,我在Wipeout中这样做的方式实际上只有一个增量分配器,但它记住
不同的关卡。所以当游戏启动时,它在零处,然后我加载一些整个游戏需要的图形,比如字体和船只的图形,这些在所有比赛中都是需要的,船只的纹理也放入增量分配器中,然后当你开始比赛时,增量分配器的位置被记录,然后我加载赛道
到这个增量分配器中。当比赛结束时,它只是重置到这个位置。赛道基本上被丢弃,为下一个赛道加载腾出空间。因此这是一个非常非常简单的内存管理方法,它有效。
我必须说,它在这个游戏中效果很好,因为你有这些非常独特的场景,它们都有自己的图形和模型。是的,你有一些模型,你只需在加载第一个场景之前加载,比如船只等等,它们会一直存在。因此是的,你永远不必释放任何东西,你知道你不能泄漏内存,因为它
重置为之前的级别。我想你回答了我主要的问题,所以我对这个概念不太熟悉,所以这真的很有趣,谢谢你深入探讨它。但我想你在这里要做的主要事情是逻辑上分组你正在加载的内容,因为你不能例如在一个块的中间释放某些东西,对吧?就像你释放的东西,你只是重置指针的某个点,而某些东西你没有范围去移除某些东西,对吧?好的。
是的,没错。所以是的,你不能从中间释放某些东西并再次腾出空间。所有在里面的东西都在里面,直到你重置指针,是的。这对于你所说的非常明显的场景来说是理想的,比如基于赛道的游戏。在Wipeout中实际上变得稍微复杂一点,因为我需要一些可以无序释放的内存。因此,例如,当你从磁盘加载赛道时,
从文件中。整个文件首先被加载,然后我遍历这个文件,查找我需要的所有数据,包括所有顶点等等。我将它们放入一个更高效的渲染结构中。
而我在游戏中使用的最终数据被放入增量分配器中。但我加载的文件,我可以稍后释放。因此,我需要一个我称之为临时分配器的东西,用于这些非常短暂的对象,你必须显式释放它们。是的,这主要用于加载文件并以某种方式转换它们。关于这一点,我认为
整个游戏在开始时为这个增量分配器分配了16兆字节的内存。增量分配器从零增长到16兆字节。而临时分配器实际上位于同一个16兆字节的空间中,但它从末尾向下增长。