如何在2026年制作一款Nintendo 64游戏

Hacker News Top 产品

摘要

作者详细讲述了他如何将自己的JavaScript游戏引擎移植到C语言,并制作出由Modretro发行的实体N64新游戏Xibalba 64——这是N64主机商业生命周期结束后推出的第二款新游戏。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/06 08:00

# 如何在 2026 年制作一款任天堂 64 游戏 来源:https://phoboslab.org/log/2026/08/xibalba64-making-of — 2026年8月4日,星期二 两年前,我毫无理由地把我的 JavaScript 游戏引擎移植到了 C 语言(https://phoboslab.org/log/2024/08/high_impact)。此后我找到了一个理由:制作一款新的 N64 游戏! 成果就是 Xibalba 64(https://modretro.com/products/xibalba-64)——一款类似《德军总部 3D》的第一人称射击游戏。Modretro 同意将其作为他们 M64(一款现代 N64 克隆主机)的实体首发游戏发行,包含卡带、包装盒和说明书! Xibalba 64 卡带和包装盒 Modretro.com 上的 Xibalba 64(https://modretro.com/products/xibalba-64) 据我所知,这是自 N64 原始商业寿命结束以来,第二款实体发行的*全新* N64 游戏。臭名昭著的 Xeno Crisis(https://modretro.com/products/xeno-crisis)由 Bitmap Bureau(https://www.bitmapbureau.com/)制作——最初是世嘉 Mega Drive 的新游戏,后来陆续登陆了许多许多其他主机——于 2023 年登陆 N64。自 2002 年《托尼·霍克职业滑板 3》之后,就没有其他新游戏在 N64 上发行过。 ## 引擎 Impact(https://impactjs.com/)是我在 2010 年开发的一款 JavaScript 游戏引擎。它为 2D 动作游戏量身定制,处理图块表、背景地图、精灵和碰撞检测。它非常简单,但依然是为你想做的任何东西提供的可靠基础。 两年前我用 C 语言重写了 Impact。为什么?我不知道。只是觉得好玩。 这个 C 移植版本,high_impact(https://phoboslab.org/log/2024/08/high_impact),有一个“平台后端”的概念。平台负责底层管道——打开窗口、创建绘制表面、读取输入等等。开箱即用,high_impact 自带两个平台后端(SDL2 和 Sokol),你可以为其中任何一个编译你的游戏。这已经让 high_impact 游戏能够在许多不同设备上运行。 high_impact 中的渲染后端也是模块化的。你可以用软件渲染器、OpenGL 或 Metal(用于 iOS/macOS)编译你的游戏。可以在不修改引擎任何其他部分的情况下添加对新平台后端或渲染后端的支持。 这是制作 N64 游戏的完美起点。 ## N64 硬件与平台库 N64 是一头古怪的野兽。除了 93 MHz 的 MIPS CPU(大端序!)之外,它还有两个协处理器来处理图形、声音等: - “Reality Display Processor”(RDP)——一个固定功能图形处理器 - “Reality Signal Processor”(RSP)——一个可编程向量处理器 两者都位于同一个物理封装中,通常被称为“Reality Coprocessor”(RCP)。 N64 RCP *图片来自 N64Brew Wiki(https://n64brew.dev/wiki/Reality_Coprocessor)* 在 N64 生命周期的头几年,任天堂严格保护对 RSP 的访问。它只被任天堂官方许可的平台库“libultra”独占使用。直到后来,任天堂才允许游戏工作室为 RSP 编写自定义“微码”(实际上只是 MIPS 汇编)。 让硬件保持开心绝非易事,而且 RDP 的指令古怪而复杂。在*裸机*上编程你的游戏几乎是不可能的。近年来,任天堂官方的“libultra”已经流传到了互联网上,但使用它可能会招致版权诉讼。 幸运的是,N64 自制软件社区在过去几年里取得了很大进展,我们现在有一个非常强大的替代品:Libdragon(https://libdragon.dev/)。 Libdragon 基本上就是 N64 的 SDL。它提供了绘制精灵和三角形、声音输出、手柄输入等功能。 我只花了几晚上就为 high_impact 在 libdragon 之上构建了一个新的平台后端。我用 Biolab Disaster(https://github.com/phoboslab/high_biolab)测试了这一点。游戏代码未作修改;性能*一般*,但我是在以最天真方式使用 N64 硬件。 ## 开发环境 Libdragon 提供了编译器和构建 N64 ROM 文件所需的一切。安装说明(https://github.com/DragonMinded/libdragon/wiki/Installing-libdragon)和所有其他文档都很全面且写得很好。该库附带了许多示例帮助你入门。 总的来说,使用 Libdragon 是一种享受。只是提醒一下:你可能想使用`preview`分支,因为“稳定”的`trunk`分支已经无可救药地落后了。 对于测试,一个好的模拟器至关重要。在很长一段时间里,N64 模拟都非常不准确。尤其是 RSP 和 RDP 协处理器的模拟不力,是大多数问题的根源。 大多数模拟器只是模拟任天堂的平台库 libultra。它们模拟的是绘制三角形的*意图*,而不是硬件实际会做什么。虽然不准确,但这在早期使得模拟*成为可能*。著名的 UltraHLE(https://en.wikipedia.org/wiki/UltraHLE)(“Ultra High Level Emulator”)在 N64 寿命期内发布,引发了许多麻烦和随后的诉讼。 如今,Ares(https://ares-emu.net/)中的 N64 核心更接近真实硬件——RDP 和 RSP 被完全模拟,包括 RSP 的精确时序。然而,N64 臭名昭著的缓慢内存带宽仍然只能在真实硬件上测试(这最近让我有些失望(https://x.com/phoboslab/status/2081117764278686138))。 所以你需要一台真正的 N64 和一个能让你玩任意` .z64` ROM 文件的卡带。开源项目 SummerCart64(https://github.com/Polprzewodnikowy/SummerCart64)非常出色,并且有许多不同的制造商生产。请注意:一些制造商(尤其是 AliExpress 上的)会在板子元件上偷工减料。 SummerCart64 有通常的 SD 卡槽来存储 ROM,但让它非常适合开发的是它的 USB-C 端口:你可以直接把它连接到 PC,并使用 sc64deployer(https://github.com/Polprzewodnikowy/SummerCart64/releases)将 ROM 作为构建过程的一部分上传。 我最终把 N64 放在 PC 旁边,通过 USB 连接,并使用一张便宜的 10 美元 USB 模拟采集卡将其视频输出显示在桌面上的一个窗口里。在 Linux 上,需要一些`mpv`的调整才能获得低延迟输出;这是我用的脚本(https://gist.github.com/phoboslab/dcebe3785f8b0992a6640b4a1597bd3f)。 有了这个设置,在真实硬件上迭代就只是编译和按下 N64 重置按钮的问题了。 ## 游戏 我最初制作 Xibalba(https://phoboslab.org/log/2014/07/xibalba-a-webgl-first-person-shooter)是在 2014 年作为我的 JavaScript 游戏引擎的演示。那时 WebGL 还是热门新事物;在浏览器中运行 3D 游戏是相当新奇的事情。游戏很短,只有少量关卡、武器和敌人类型。 相比之下,我希望 Xibalba 64 成为一款*真正的*游戏,而不仅仅是演示。所以我不仅需要把游戏移植到 C 和 high_impact,还要用更多关卡、更多敌人类型和更多武器来扩展它。 `high_impact`是一个 2D 游戏引擎,但 Xibalba 64 显然是 3D 的。嗯,不完全是。由于游戏没有高度变化,它可以*大部分*被视为 2D。你可以在概念上从 2D 俯视视角来玩 Xibalba 64。当然,那不会那么令人兴奋,但所有的物理、移动和射击都会以同样的方式工作。在这方面,游戏与《德军总部 3D》非常相似。 high_impact 的许多物理函数期望一个带有`.x`和`.y`分量的`vec2_t`参数。但为了绘制,我绝对需要 3D 位置,所以我想出了这个`vec3_t`类型的定义,并修改了`entity_t`类型: `` typedef struct { float x, y; } vec2_t; typedef union { vec2_t xy; struct { float x, y, z; }; } vec3_t; typedef struct { // ... vec3_t pos; vec3_t vel; // ... } entity_t; `` 现在,每当我需要调用一个接受`vec2_t`的函数时,我可以免费从`vec3_t`“转换”: `` trace_t res = trace(collision_map, entity->pos.xy, entity->vel.xy); `` 由于内部的`vec3_t``struct`是“匿名的”,我仍然可以直接访问所有值;即`entity->pos.z`可以正常工作。 现有关卡和敌人类型的初始移植相当顺利,大约两周就完成了。然后我又花了几个月来扩展游戏和优化渲染器。 Libdragon 的大多数函数自然地适配到新的平台和渲染后端,尽管我不得不修改 high_impact 的其他部分以绕过其混音器(Libdragon 有自己的,由 RSP 加速)和图像加载器。 在整个过程中,我保留了使用 SDL2 或 Sokol 后端构建游戏的能力。这对于试玩测试游戏逻辑和敌人行为非常有用。为了制作关卡,我还实现了一个简单的热重载机制,每当关卡文件发生变化时触发。 high_impact 自带的关卡编辑器是一个独立的 HTML 文件。我最终对其进行了相当多的扩展,以更好地支持光照贴图、为实体显示实际精灵(而不仅仅是方框)、为实体设置添加描述以及提供其他小功能。唯一的真理来源仍然是 C 源代码——关卡编辑器读取它并自动提取实体类型和支持的设置。 Xibalba 64 关卡编辑器 由于关卡编辑器仍然使用 JSON 文件,我构建了一个小型地图编译器,读取 JSON 并输出二进制数据。虽然在 N64 上加载 JSON 当然可行,但会增加大约 100 毫秒的不必要加载时间。所以在构建过程中,每个 JSON 关卡文件被转换为一个本质上看起来像这样的结构体: `` typedef struct { uint16_t magic; uint16_t entities_len; uint16_t map_width; uint16_t map_height; struct { uint16_t type_id; uint16_t x; uint16_t y; uint16_t settings_len; struct { uint16_t setting_type; // 如 "name", "target", "size", ... union { float16_t float_value; int16_t int_value; struct { int16_t string_len; char string_value; }; } value; } settings[settings_len]; } entities[entities_len]; uint16_t collision_map[map_width * map_height]; uint16_t floor_map[map_width * map_height]; uint16_t wall_map[map_width * map_height]; uint16_t ceiling_map[map_width * map_height]; uint16_t light_map[map_width * map_height]; } level_t; `` 关卡编译器以 N64 的大端格式和 x86(SDL2、Sokol、WASM)的小端格式写入这些值,因此我们可以在所有平台上轻松读取所有内容,而无需字节交换。 ## 渲染 Libdragon 本身有一个绘制三角形的函数:`rdpq_triangle()`将单个三角形绘制调用插入 RDP 队列。虽然这可行,但你真正想做的是将绘制调用提交给 RSP,让一些自定义微码执行变换、光照、深度计算等,然后让 RSP 指示 RDP 最终绘制三角形。 RDP 和 RSP 的复杂性对我来说仍然很陌生,但幸运的是另一个杰出的开源库 Tiny3D(https://github.com/HailToDodongo/tiny3d)通过一个易于使用的 API 处理了这一切以及更多。让东西出现在屏幕上是最容易的部分;让它达到高性能则是另一回事。 N64 臭名昭著地只有 4 KB 纹理内存。你能上传的最大纹理只有可怜的 64×64 像素。更糟糕的是,纹理上传的内存延迟极其糟糕。《超级马里奥 64》和许多其他游戏使用的解决方案是尽可能渲染无纹理多边形。 这在我游戏的风格上不太行得通,所以我必须在关卡图块的绘制顺序上非常小心,以尽量减少纹理上传。除此之外,Tiny3D 可以一次加载并提交最多 17 个四边形,不利用这一点是浪费的。所以我最终在 64 位绘制调用中收集具有相同纹理的三角形批次: `` typedef union render_call { uint64_t packed; uint32_t hashable; uint64_t ident : 46; struct { uint64_t translucent : 1; uint64_t texture_index : 9; uint64_t x : 10; uint64_t y : 10; uint64_t w : 8; uint64_t h : 8; uint64_t vbi : 14; uint64_t len : 4; }; } render_call_t; `` 这里`vbi`是相应的顶点缓冲区索引,`len`是此调用中四边形的数量。由于每个调用只有 64 位宽,我们可以在帧结束时高效地对它们进行排序,并将它们提交给 Tiny3D。 但在做这一切之前,我必须先弄清楚关卡的哪些部分实际上是可见的。最初的 JavaScript Xibalba 使用传送门系统,将每个关卡划分为扇区,并预先计算从当前扇区可见的扇区。这工作得很好,但产生的过度绘制比我期望的要多一些。 所以我选择了另一种方法:光线投射。是的,游戏只是向场景投射 320 条光线,覆盖整个视野。每条光线在位图中标记穿过的图块,以便提交给渲染器。 后来,我通过递归划分 320 像素的视野,直到两条光线击中同一个图块,进一步优化了光线投射。在这个过程中,我还检查任何被穿过的图块是否缺少天花板——如果是,我们必须绘制天空盒。 有趣的事实:Xibalba 64 中的天空盒只是一个 32×32 像素的纹理,被优美地涂抹在地平线上。 Xibalba 64 天空盒 作为另一项优化,我将每个无法一次上传的图块表排列成单列,并尽可能只使用 4 位索引颜色。使用更少的颜色允许更多像素适应纹理内存,而列布局确保每个图块可以作为单个连续的内存块上传。 Xibalba 64 精灵表 有了所有这些,游戏以稳定的 60 FPS 运行。这是许多其他 N64 游戏都无法声称的! 四人分屏模式并非始终达到 60 FPS 标记,但仍然保持流畅。相比之下,《黄金眼 007》臭名昭著地经常掉到个位数帧率。 Xibalba 64 四人死亡竞赛 ## 声音与音乐 与我所有其他游戏一样,我的好朋友 Andreas Lösch 制作了一些出色的音乐。你可以聆听完整的 Xibalba 64 原声带 Bandcamp(https://nofatenetmusic.bandcamp.com/album/xibalba64)。 游戏本身还有一个内置音乐播放器,你可以在单人战役中解锁。 卡带空间很紧张,即使是压缩音频也通常要么相当大,要么解码成本太高。 在一项英勇的努力中,Giovanni Bajo——Libdragon 的维护者之一,也是 N64 相关事务的绝对巫师——实现了一个 RSP 加速的 Opus 解码器。作为背景:Opus 是一种于 2012 年首次发布的音频编解码器。那是 N64 之后 16 年(!)。它能工作就已经是个奇迹,但遗憾的是它在游戏过程中使用仍然有点太耗费计算资源。 (*题外话:*Giovanni Bajo 后来还为 N64 实现了一个实时 H.264 解码器。) 目前更好的选择是一种简单的 4 位 VADPCM 格式,Libdragon 在播放期间在 RSP 上透明地解码它。压缩率当然不高,但比未压缩的 WAV 好得多。32 MB ROM 中约有 31 MB 被声音和音乐占用。 在 Xibalba 64 发布后,我开始实现另一种音频压缩格式,以更好地解决空间/质量/复杂性之间的权衡。更多内容将在下一篇博客文章中介绍! ## 发布与销售 Modretro 之前制造了一款 Game Boy Color 克隆机——Chromatic——兼容所有现有

相似文章

这个周末你打算做什么?

Lobsters Hottest

一位开发者描述了将《完美黑暗64》关卡移植到 noclip.website 的过程,强调了读取 N64 显示列表和重新实现渲染引擎的挑战。

Ogre Battle 64 重编译项目进度99.05%

Hacker News Top

本项目旨在使用N64Recomp工具链将N64游戏Ogre Battle 64静态重编译为原生PC可执行文件,主要代码段已完全重编译。工作主要由DeepSeek v4/v4.1 Flash AI模型完成。

Nintendo 64 Partner-N64 开发套件

Hacker News Top

深入介绍京都微计算机(KMC)制造的 Nintendo 64 Partner-N64PC 开发套件,详解这台经过特殊改装的 N64 主机如何通过重新布线的 IRQ 和 RESET 关联线路,在游戏开发过程中实现实时断点调试与硬件复位。

看 Windows CE 在 N64 上运行

Lobsters Hottest

开发者 Throaty Mumbo 借助 Nintendo 64 的 MIPS 处理器和 1MB 内存,将 Windows CE 原生移植到了该主机上,并在 YouTube 和 GitHub 上记录并公开了整个过程与项目文件。