For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主导航
2026年9月4日 Codex

使用 Astra 构建游戏

我如何在 Codex 中构建一款程序化生成的太空探索游戏,从概念图到可以降落的行星。

使用 Astra 构建游戏

Astra 已经很擅长将我脑中的想法转化为玩法和美术方向。我一直在 Codex 中用它构建 Void Explorer。这是一款太空探索游戏,您可以从遥远的恒星一路飞到外星地表。

Void Explorer 的游戏实录集锦。

游戏中有 2,048 个恒星系统和超过 10,000 颗程序化生成的行星,其中包括与地球一样大的星球。每颗可见的恒星都是这个宇宙的一部分,都可以选作目标。您可以选中远处的一个光点,然后飞往那里。

您可以从太空接近一颗行星,穿过大气层,持续下降,直到飞行在海岸线上空。您可以降落、走出飞船、四处走走,然后再次起飞。要让这段旅程成为现实,就必须统筹解决尺度、地形、操控和渲染问题。

我在文中收录了一些开发过程中使用的提示,并做了精简和润色。它们展示了我如何描述自己的需求,以及技术实现如何由此展开。

从体验出发

我最初的需求描述说明了玩家应该能做些什么:

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

这给 Astra 提供了一些具体约束。恒星不能只是画在背景上的光点。行星不能在我靠近时变成一个独立关卡。旅行必须跨越真实距离,再用虚构的脉冲航行和超空间引擎,让这些距离在游戏中可以实际跨越。

在游戏的大部分内容尚未构建之前,我还用图像生成来探索视觉风格。最初的概念图过于写实,接下来的方向又太简单。这是我提出的一次修改意见:

现在有点过于简化了。我们需要一个折中的方案:更好的配色、霓虹灯光,以及能营造深空感的更强对比。让我看看高速飞行时的样子,飞船周围要有恒星和尘埃。

得到满意的图像后,我让 Astra 保存它们,并将这个方向转化为游戏的参考图。我们为轨道飞行、高速飞行、进入大气层和降落都设定了具体目标。有了这些图像,评判下一个版本就容易多了。

生成的概念图:一艘象牙白飞船位于有棱面质感的青色行星上方,画面中有品红色点缀和两颗暖色太阳

生成的概念图,确立了游戏的配色和视觉方向。

让 Astra 提出架构方案

我设定约束,Astra 提出实现方案。应用使用 TypeScript 和 Vite,并用 Three.js 进行渲染。这样就能通过代码直接控制网格、材质、光照和程序化几何体,而这款游戏的大部分视觉工作都集中在这些方面。

第一个可玩版本的渲染器使用了 WebGL2。后来,我问 Three.js 是否限制了画面表现。Astra 建议保留模拟系统,将渲染器迁移到 Three.js 的 WebGPU 架构。我们保留了宇宙、导航和地形系统,只更换了渲染层。

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

地形生成在 Web Workers 中运行,因此可以在负责输入和绘制的线程之外准备几何体。Vitest 覆盖了生成结果的可重复性、坐标运算和地形契约等测试,Playwright 则在浏览器中测试游戏。这让 Astra 能够检查底层系统,而我继续评估游戏的实际体验。

整个过程中,美术方向始终是我们追求的目标。行星周围的大气、星环的辉光、恒星发出的彩色光线,以及 Neon Phosphor 特效,共同塑造了游戏的视觉风格。这个滤镜增添了复古显示器的质感,同时保持导航信息清晰可读。

Void Explorer 游戏画面,展示四翼飞船、青色行星、品红色星环、双太阳和导航显示界面

启用 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 帧轨道测试:

测量指标改动前改动后
场景绘制调用总次数11977
场景三角形总数48,80961,092
平均帧间隔251.48 ms199.26 ms

这次对比使用无头 Chromium 和 SwiftShader 软件渲染,测试时间早于下文展示的四翼飞船。它衡量的是该测试环境中的变化,并非我的 GPU 上的帧率。这些工具为 Astra 提供了浏览器计时数据、渲染图像和资源数量,但没有测量单条 GPU 指令或着色器的执行时间。

Astra 调查了加载和下降过程中的工作量:生成了多少几何数据,工作线程与渲染器之间传输了多少数据,以及地形任务在派上用场之前被丢弃的频率。

其中一项改动是根据远处行星在屏幕上的大小来分配细节。占满视野的行星需要立即呈现良好的轮廓,而小于一个像素的行星则不需要同等精细的几何结构。在对起始场景进行的受控对比中,调整后的策略使用了 7,040 个代理模型三角形,原先则为 51,200 个。两种策略使用相同的几何生成器,因此可以单独衡量加载决策的影响。

另一项改进则完全保留了地形三角形的数量。索引网格复用相邻三角形共享的顶点,无须重复存储其位置、颜色和法线数据。在地形质量设为“高”的测试中,六个地面层传输的缓冲区数据从约 35 MB 降至 15 MB,同时保留了 241,952 个三角形。地面和景物也被拆分为独立任务,因此地面几何数据可以先准备好,无须等待装饰物。

地形选择器也存在无效工作。它可能先请求一些区块,随后改变决策,将其丢弃,再请求替代区块。Astra 让这些细化决策更加稳定,并且只有在预算足以容纳全部四个子瓦片时才细化一个瓦片。在工作线程延迟固定的受控模拟中,经过六秒下降和随后三秒的稳定等待,被丢弃的任务从 6,074 个降至 13 个。

折线图展示了九秒模拟时间内被丢弃的已调度地形任务数。旧版选择器达到 6,074 个任务,改进后的选择器则保持在 13 个。摄像机在六秒后停止移动。

在使用两个工作线程、模拟工作线程延迟为 100 ms 的受控模拟中,被丢弃的已调度任务从 6,074 个降至 13 个。这里衡量的是地形调度,而非帧率。

仅靠工作线程无法解决所有卡顿。主线程上的地形生成回退逻辑也需要让出执行时间。Astra 将生成过程拆分为可恢复执行的步骤,每帧预算约为 4 ms,这样大型地形任务就不必在一次不间断的执行中全部完成。渲染器还将游戏世界的分辨率上限与界面分开控制,使导航文字在降低画质时仍保持清晰。

这些地形测量结果表明,几何数据量、传输数据量以及被丢弃的工作都减少了。它们并不是硬件帧率基准测试。要检验着色器编译、GPU 数据上传、运动表现以及过渡的实际视觉效果,仍然需要在浏览器中试玩。

我提供的是试玩反馈:哪里感觉慢、哪里看起来不对,以及我想改进什么。Astra 追踪代码、编写可重复运行的地形测试脚本,并记录测量结果。我认可方向后,它就落实改动,再次运行相同的测试来比较结果。我不需要规定每一步,它就能完成调查和实现,而我则持续检查游戏的视觉效果和操作体验。

将概念图转化为游戏资产

这个宇宙大部分由代码生成,包括行星、地形、云层、星环和恒星。玩家飞船是主要的单独建模制作的 3D 资产,我对它采用了不同的制作方式。

在将飞船放入游戏之前,我通过生成的概念图和 Blender 渲染图来检查它的效果。当机翼不符合我的设想时,我要求提供更有用的参考图:

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

我选了一张参考图,图中的飞船有四片独立机翼,外形对称,翼尖带有粉色灯光。接着,我让 Astra 在 Blender 中制作一个新模型,保持游戏的美术风格,并将其加入游戏。

这个过程包括根据参考图构建几何结构、检查轮廓和材质,以及准备运行时资产。Blender 源文件包含 193 个可编辑网格。导出的飞船有 14,968 个三角形,分为八个不透明材质批次。我可以要求增加模型细节,同时让 Astra 控制导出模型的渲染开销。

生成的俯视概念图,展示四片独立机翼、粉色翼尖灯、象牙白船体和绿色座舱盖

我认可的生成版飞船概念图。

飞船的 Blender 渲染图,展示陶瓷船体、四片独立机翼和两个青色引擎

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

尝试程序化水体

我还花了很多时间在 Sunwake 中尝试水体效果。这款游戏让玩家驾驶小船穿行于程序化生成的海洋。我想要棱面分明的波浪、流畅的运动,以及彩色玻璃般的色彩。Astra 在 Three.js 中构建了自定义水体渲染器,用同一套波浪模型驱动可见海面和船只浮力。船体随波浪升沉、俯仰和横摇,局部模拟则负责涟漪和泡沫,船只驶过时会留下尾流并激起水花。后来,我让 Astra 在 Blender 中制作一艘细节更丰富的船,并将其导入游戏。这与 Void Explorer 的思路相同:通过模拟,将程序化环境与单独建模制作的载具连接起来。

Sunwake 游戏画面:粉色朝阳下,一艘橙色小船驶过棱面分明的蓝色波浪,身后留下带有泡沫的尾流

Sunwake 将程序化海洋与在 Blender 中单独建模制作的船只结合起来。

Hollowflux 则沿着另一个方向展开:这是一款围绕发光地下河构建的小型 2D 动作 RPG。洞穴、角色和装备都由代码绘制,没有导入精灵图集。我不断让 Astra 改进行走、冲刺和攻击对水面的扰动,让效果更真实可信。由单元格组成的网格跟踪水面高度、水流、泡沫和电荷。脚步留下涟漪,长矛刺击划出狭窄的尾流,锤击则激起一圈圈波纹。水流沿河岸回旋,将泡沫带向下游,同时也推动玩家和敌人。水还会影响战斗:鳗鱼释放的电流会沿相连的水体传播,因此玩家踏上干燥的石头就能避开这次电击。

Hollowflux 中,一位像素风冒险者涉水穿过发出蓝绿色光芒的河流,周围是涟漪、生物和幽暗的洞壁

Hollowflux 用代码绘制游戏美术画面,其中的水体会对移动和战斗作出反应。

制作与分享游戏

我喜欢与 Astra 合作的一点是,我可以从自己想要的体验出发,用图像明确视觉方向,再根据实际试玩提供反馈。一次关于行星消失的反馈,可能带来流式加载、几何结构和移动逻辑的改动。一个让水体更真实可信的要求,可能变成视觉效果和游戏玩法共用的模拟系统。我仍然需要判断体验是否合适,但 Astra 能将这些决定落实到代码、资产和测试中。

分享浏览器游戏也很简单。使用站点插件,您可以这样请求 Astra:

使用 @Sites 部署这个游戏,并给我一个链接。

发布时设为公开访问,任何人都可以直接在浏览器中游玩。

您可以在浏览器中游玩 Void ExplorerSunwakeHollowflux

如果您心里一直有个游戏点子,不妨试着用 Astra 做出一个可玩的小版本。从一个您想打磨好的体验点入手,提供几张视觉参考图,再测试结果。一艘船、一个房间,或一次交互,都足以作为起点。亲自试玩,具体说明想改什么,再以此为基础继续构建。