跳到正文
北京时间
原文
OpenAI Developers:Blog(网页)·· 26 天前精选AI 评分63

OpenAI 工程师用 Astra 在 Codex 中构建太空游戏 Void Explorer 的实践详解

Building games with Astra

AI 导读

OpenAI 开发者博客分享了用 Astra 在 Codex 中构建太空探索游戏 Void Explorer 的完整过程。游戏包含 2,048 个星系和超过 10,000 颗程序化生成的行星,可从太空穿越大气层降落并步行。

推荐理由

原文以 Void Explorer 为例,展示了如何用 Astra 从体验描述、视觉参考到可测试的浏览器接口完成游戏开发,方法可迁移。

正文 · AI 翻译

Astra 已经非常擅长把我脑海中的想法转化为玩法和美术方向。我一直在 Codex 中使用它来构建 Void Explorer,这是一款太空探索游戏,你可以从遥远的恒星一路旅行到外星地貌。

Void Explorer 的玩法精彩集锦。

游戏包含 2,048 个恒星系和超过 10,000 颗程序化生成的行星,其中包括地球大小的世界。每一颗可见的恒星都属于这个宇宙,并且可以被选中。你可以选定远处的一个光点,然后飞向它。

你可以从太空接近一颗行星,穿过它的大气层,持续下降,直到飞越一条海岸线。你可以着陆、走出飞船、四处走动,然后再次起飞。要让这段旅程真正跑通,意味着必须同时解决尺度、地形、操控和渲染的问题。

我收录了构建过程中的几条提示词,为了篇幅和清晰度做了编辑。它们展示了我如何描述自己想要的东西,以及技术工作如何由此展开。

从体验出发

我最初的简报描述了玩家应该能够做到的事情:

我能看到的一切都应该是可以抵达的。保持真实的距离,然后通过尺度和速度让旅行变得可行。我想从太空飞入一颗行星的大气层,再一路降落到地面。行星可以像地球一样大,所以我们需要程序化地形和分块渲染器。

这给了 Astra 一些具体的约束。一颗恒星不能只是画在背景上的一个点。一颗行星不能在我接近它时变成一个独立的关卡。旅行必须覆盖真实的距离,而虚构的脉冲旅行和超空间引擎让这些距离变得可行。

在构建游戏的大部分内容之前,我还用图像生成来确定外观。最初的概念太写实了。接下来的方向又太简单了。这是我的一次修正:

现在这有点太简单了。我们需要一个中间地带:更好的色彩、霓虹光,以及更多能唤起深空感的对比。给我看看高速飞行时、飞船周围有恒星和尘埃的样子。

一旦我喜欢上了这些图像,我就让 Astra 把它们保存下来,并将这个方向转化为游戏的参考。我们为轨道飞行、高速飞行、大气层进入和着陆设定了具体目标。这些图像让评判下一个构建版本变得容易多了。

Generated concept art of an ivory ship above a faceted cyan planet, with magenta accents and two warm suns

生成的概念艺术确立了游戏的调色板和视觉方向。

让 Astra 提出架构

我设定约束,Astra 提出实现方案。应用程序使用 TypeScript 和 Vite,并用 Three.js 进行渲染。这让代码可以直接控制网格、材质、光照和程序化几何体,而这正是这款游戏大部分视觉工作发生的地方。

第一个可玩的渲染器使用 WebGL2。后来,我问 Three.js 是否在拖累视觉效果。Astra 建议保留模拟部分,并将渲染器迁移到 Three.js 的 WebGPU 架构。我们保留了宇宙、导航和地形系统,同时更换了渲染层。

渲染器使用 Three.js 的节点材质和 Three.js 着色语言(TSL)在代码中定义大气、水和光照效果。

地形生成在 Web Worker 中运行,因此它可以在处理输入和绘制的线程之外准备几何体。Vitest 覆盖可重复生成、坐标数学和地形契约等内容。Playwright 在浏览器中测试游戏。这给了 Astra 检查底层系统的方法,而我则继续审视游戏的感受。

艺术指导始终是目标。行星周围的大气、行星环的光晕、恒星发出的彩色光线以及 Neon Phosphor 效果共同塑造了整体外观。该滤镜增添了复古显示纹理,同时保持导航清晰可读。

Void Explorer gameplay showing the four-wing ship, a cyan planet, magenta rings, twin suns, and the navigation display

启用 Neon Phosphor 效果的游戏截图。

给 Astra 一种检查和游玩的方式

我自己一直在玩这个游戏,但 Astra 也需要一些方法来调查问题,而不仅仅是阅读我的描述。游戏暴露了一个小型 JavaScript 接口 window.__VOID_EXPLORER__,浏览器测试可以调用它来检查当前状态:我正在接近哪个天体、飞行模式、地形就绪情况以及当前激活的相机。它还暴露了渲染和流式加载计数器,包括绘制调用、三角形数量、排队中的地形任务以及缓冲的地形数据。

游戏为轨道、脉冲旅行、大气下降和海岸着陆设置了命名的测试场景。这让 Astra 无需每次都飞越整个宇宙,就能回到一个有用的起点。Playwright 可以加载场景、等待地形就绪、截取屏幕截图,并检查其背后的状态。单独的旅程测试使用真实控制,并在游戏运行时记录位置和状态变化,因此设置一个着陆场景并不能替代对着陆本身的测试。

这些测试覆盖着陆、离开飞船、行走、保存和重新加载、登船以及起飞。它们能捕捉到仅凭截图无法发现的问题,例如尚未就绪的碰撞表面,或在过渡期间在位置之间跳跃的飞船。

我还可以让 Astra 检查我正在游玩的浏览器标签页。当我询问 Aurelia 的云层是否仍然可见时,它截取了我当前的视图并读取了屏幕上的飞行信息。这些是特定时刻的检查;Astra 并没有持续观察我飞行的每一帧。

这个循环是:重现问题、检查截图和状态、追踪相关代码、进行修改,然后重新运行检查。如果再做一款游戏,我会尽早构建这些工具:几个可重复的场景、有用的状态和性能计数器,以及针对主要交互的浏览器测试。它们让 Astra 能够独立调查和测试更改,而我则继续审查外观和操控手感。

在多个尺度上表示宇宙

拥有数千颗行星并不意味着要加载数千个详细网格。一颗行星最初只是一个描述:它的半径、轨道、大气和种子。种子使其地形可重复生成。游戏会在我接近的区域周围生成几何体,并且当我返回时能够再次生成相同的景观。

坐标也需要类似的谨慎处理。光年距离和站在飞船旁边的人处于截然不同的尺度。将这些巨大的位置直接发送给 GPU 会丢失地面附近所需的精度。

Astra 将物理地址与用于绘制的坐标分离开来。宇宙使用大整数单元格和较小的局部偏移。在渲染之前,游戏会减去观察者的位置,因此相机保持在原点,附近物体具有较小的坐标。显示的距离和相对大小保持一致。

运动部件还需要在时间上保持一致。恒星、行星和卫星遵循解析轨道,渲染器在与观察者相同的模拟时间评估它们的位置。停泊的飞船保持附着在旋转的行星上。恒星的位置和颜色为光照提供输入,因此双星日落中的两颗太阳与我从轨道上看到的是同一批恒星。

让下降过程保持连续

在 Void Explorer 中从太空飞向卫星表面。

从太空到地面的过渡经历了大量迭代。其中一条提示来自以浅角度接近行星时:

当我几乎平行于行星接近时,我仍然可能飞得太快,而且地形加载时行星几乎消失。我们需要同时修复速度曲线和地形流式加载。大气层能否缓和远处 LOD 与详细地形之间的过渡?行星在接近过程中绝不应消失。

飞行控制器现在对浅角度接近施加单独的速度上限,依据高度以及由行星半径和重力估算的轨道速度。在更接近地面时,它还会采样前方地形以调整制动。

游戏使用多个细节层级,即 LOD。从远处看,行星是一个开销很低的多面球体,称为代理。当它占据更多屏幕时,渲染器会添加细节。接近表面时会激活由六个立方体面投影到球体上构建的地形。每个面可以反复分裂为四个更小的正方形:一棵四叉树。

这使游戏能够在可见区域和行进方向上细化地形,而无需以步行分辨率生成地球大小的表面。靠近地面时,局部区块为飞船和玩家周围提供更精细的几何体。

所有这些表示都采样同一个底层行星。一个共享函数组合了大陆、山脊、陨石坑和更小的起伏。它提供海拔、水、生物群系以及景观使用的其他信号。粗网格和细网格以不同分辨率近似该数据,并共享颜色策略以保持行星外观一致。

交接与生成同样重要。远处行星保持可见,直到所有六个粗略地形面都准备就绪。一个地形瓦片保持原位,直到它的四个子瓦片都准备就绪。在过渡期间,互补的像素掩码将每个像素分配给旧表面或新表面。这避免了在替代表面加载时让整个行星变透明。

大气层随物理高度变化,云层在行星地理上方移动。它们有助于将轨道视图与景观连接起来。渲染器只在更精细地形准备就绪的地方移除粗略覆盖。随着更精细几何体出现,山峰轮廓仍可能发生变化。

着陆提出了更严格的要求。接触区块的可见地面、碰撞三角形和液体危险必须全部来自同一次生成,并一起准备就绪。行走使用 Rapier 在一个小型局部物理世界中进行。飞船的着陆逻辑单独检查其支脚、坡度和离地间隙与地形的关系。

这里有一个刻意的限制:行星使用高度场,每个表面方向只有一个海拔值。这适合大型景观,但不提供洞穴、悬垂或可破坏隧道。

测量慢帧背后的工作量

我的一些反馈将外观和性能结合在一起,因为我在同一次飞行中同时看到了这两个问题:

你能看看当我从太空飞向地面时,我们是如何加载和渲染一颗行星的吗?远处的球体和详细的地形看起来不一致,过渡也很突兀,尤其是颜色变化的时候。我想了解是什么导致了这些视觉跳变,以及我们可以在哪里减少加载更多细节所需的工作量,让下降过程看起来更好、运行更流畅。

Astra 使用 Three.js 的渲染器计数器来检查绘制调用、三角形、几何体和纹理。一个 Playwright 测试收集了 90 个帧间隔,并报告了平均值和百分位计时以及这些计数。对于视觉问题,测试还可以在启用和禁用特定效果的情况下比较渲染像素,例如有和没有隐藏重叠地面的遮罩时的地形。这有助于隔离出地面消失的原因,而不是仅仅依赖损坏场景的截图。

早期的一个飞船迭代版本说明了这些测量为何有用。用 Blender 中构建的第一个 AURORA 模型替换程序化飞船后,场景的三角形数量增加了,但绘制调用减少了。Astra 在 WebGPU 后端上对更改前后运行了相同的 90 帧轨道测试:

测量项 之前 之后
场景总绘制调用 119 77
场景总三角形 48,809 61,092
平均帧间隔 251.48 ms 199.26 ms

此比较使用了带 SwiftShader 软件渲染的无头 Chromium,并且早于下面显示的四翼飞船。它测量的是该测试环境中的变化,而不是我 GPU 上的帧率。这些工具为 Astra 提供了浏览器计时、渲染图像和资源计数;它们没有测量单个 GPU 命令或着色器执行时间。

Astra 调查了加载和下降过程中发生的工作:生成了多少几何体,有多少数据在工作线程和渲染器之间移动,以及地形任务在变得有用之前被丢弃的频率。

其中一项更改是根据远处行星在屏幕上的大小来分配细节。填满视野的行星需要立即拥有良好的轮廓。小于一个像素的行星不需要相同的几何体。在受控的起始场景比较中,修订后的策略使用了 7,040 个代理三角形,而不是 51,200 个。两种策略使用相同的几何体生成器,因此这隔离了加载决策。

另一项改进保持了完全相同的地形三角形数量。索引网格重用相邻三角形共享的顶点,而不是重复它们的位置、颜色和法线数据。在测试中高地形质量下的六个地面层中,传输的缓冲区从约 35 MB 降至 15 MB,同时保留了 241,952 个三角形。地面和景物也成为了独立的任务,因此地面几何体无需等待装饰即可准备就绪。

地形选择器中也有浪费的工作。它可能会请求块、改变决定、丢弃它们,并请求替换。Astra 稳定了这些细化决策,并且只有在预算允许所有四个子块时才细化一个瓦片。在具有固定工作线程延迟的受控模拟中,六秒的下降随后三秒的稳定产生了 13 个被丢弃的任务,而不是 6,074 个。

Line chart of discarded scheduled terrain jobs over nine simulated seconds. The previous selector reaches 6,074 jobs; the revised selector stays at 13. The camera stops after six seconds.

在具有两个工作线程和 100 ms 模拟工作线程延迟的受控模拟中,被丢弃的已调度任务从 6,074 个降至 13 个。这测量的是地形调度,而不是帧率。

单靠 Worker 并不能解决所有卡顿。主线程上的地形回退逻辑也需要让步。Astra 将生成过程拆分为可恢复的步骤,每帧预算大约 4 毫秒,因此大型地形任务不必在一个不间断的块中完成。渲染器还将世界分辨率与界面分开限制,在视觉质量降低时仍保持导航文字清晰。

这些地形测量结果显示了几何体更少、传输数据更少、丢弃的工作更少。它们不是硬件帧率基准测试。浏览器试玩对于着色器编译、GPU 上传、运动以及过渡的实际观感仍然很重要。

我的输入是来自试玩的反馈:什么感觉慢、什么看起来不对、以及我想改进什么。Astra 追踪了代码,编写了可重复的地形测试脚本,并记录了测量结果。一旦我批准了方向,它就实施更改并重新运行相同的测试以比较结果。它在我没有规定每一步的情况下完成了调查和实施,而我则持续审查游戏的外观和感觉。

将概念艺术转化为游戏资产

宇宙大部分是在代码中生成的:行星、地形、云层、光环和恒星。玩家飞船是主要的手工制作的 3D 资产,我对它采取了不同的方法。

我使用生成的概念图和 Blender 渲染图在飞船进入游戏之前进行审查。当机翼与我心目中的样子不符时,我要求提供更有用的参考:

从多个角度生成这艘飞船的清晰视图。注意前翼和后翼。我们将用这些图像来重制 3D 模型。我不喜欢当前模型上连在一起的圆润机翼。

我选择了具有四个独立机翼、对称形状和尖端粉色灯光的参考图。然后我让 Astra 在 Blender 中构建一个新模型,保持游戏的艺术风格,并将其添加到游戏中。

这涉及将参考图转化为几何体、审查轮廓和材质,并准备运行时资产。Blender 源文件有 193 个可编辑网格。导出的飞船有 14,968 个三角形,分为八个不透明材质批次。我可以在模型中要求细节,而 Astra 则控制导出的渲染成本。

Generated top-view concept showing four separate wings, pink wingtip lights, an ivory hull, and a green canopy

我批准生成的飞船概念图。

Blender render of the ship's ceramic hull, four separate wings, and twin cyan engines

游戏中使用的模型的 Blender 渲染图。

试验程序化水体

我还花了很多时间在 Sunwake 中试验水体,这是一款驾驶小船穿越程序化海洋的游戏。我想要多面体波浪、平滑的运动和彩色玻璃的颜色。Astra 在 Three.js 中构建了一个自定义水体渲染器,用相同的波浪模型驱动可见的海面和船的浮力。船体随着波浪上升、俯仰和横滚,同时局部模拟产生涟漪和泡沫,船留下尾迹和水花。后来我让 Astra 在 Blender 中构建一艘更详细的船并将其带入游戏。这与 Void Explorer 的方法相同:程序化环境加上手工制作的载具,通过模拟连接起来。

Sunwake gameplay showing an orange boat leaving a foamy wake across blue faceted waves under a pink sunrise

Sunwake 将程序化海洋与在 Blender 中构建的手工船结合在一起。

Hollowflux 走了另一个方向:一款围绕发光地下河构建的小型 2D 动作 RPG。洞穴、角色和装备都用代码绘制,没有导入精灵图。我不断让 Astra 让行走、冲刺和攻击更逼真地扰动水面。一个网格单元追踪水位、水流、泡沫和电荷。脚步留下涟漪,长矛突刺划出狭窄尾迹,锤击发出环形波纹。水流沿着河岸卷曲,将泡沫带向下游,同一股水流也会推动玩家和敌人。水还影响战斗:电鳗的放电会通过相连的水传播,因此踩在干燥的石头上可以保护玩家免受放电伤害。

A pixel adventurer wades through a glowing teal river in Hollowflux, surrounded by ripples, creatures, and dark cave walls

Hollowflux 用代码绘制游戏美术,水面会对移动和战斗做出反应。

构建和分享游戏

我喜欢与 Astra 合作的地方在于,我可以从想要的体验出发,用图像让视觉方向具体化,并根据实际游玩给出反馈。对一颗消失行星的抱怨可以引发对流式加载、几何体和移动的修改。对更逼真水面的要求可以变成视觉和玩法共享的模拟。我仍然需要判断它感觉对不对,但 Astra 可以把这些决定贯穿到代码、资源和测试中。

分享浏览器游戏也很简单。使用 Sites 插件,你可以让 Astra:

以公开访问权限发布它,任何人都可以直接在浏览器中游玩。

你可以在浏览器中游玩 Void Explorer、Sunwake 和 Hollowflux。

如果你心里有一个游戏想法,试着用 Astra 构建一个小型可玩版本。从一件你想让它感觉对的事情开始,给它一些视觉参考,然后测试结果。一艘船、一个房间或一次交互就足以开始。玩一玩,具体说明你想改变什么,然后继续在此基础上构建。

来源:OpenAI Developers:Blog(网页) · developers.openai.com