Build dossier · 20

工程深度6 分钟阅读

Game:和老朋友远程打一局红白机

选一款小时候玩过的 NES 游戏,把房间链接发给朋友,边语音或视频聊天,边远程打完那一局。

#NES · #WebAssembly · #WebRTC · #双人游戏 · #自托管2026/GAME

最近做了一个网页版 NES 游戏站。打开页面,选一款小时候玩过的游戏,创建双人房间,再把链接发给朋友。对方进入以后,可以一边语音或视频聊天,一边在同一局游戏里分别控制 P1 和 P2。

不需要远程控制对方的电脑,也不是把游戏画面当视频直播。两个浏览器各自运行同一个 Rust/WebAssembly 模拟器,通过 WebRTC 交换手柄输入和同步状态。摄像头与麦克风也是可选的,关掉以后仍然可以正常联机。

小时候两个人要坐在同一台电视前,共用一对手柄。现在大家可能已经不在同一个城市,但发一条链接,还是可以把那一局接起来。这是这个项目最想实现的体验。

当前可用的游戏功能

进入游戏站后,可以浏览中文游戏目录,也可以搜索游戏。选中一款后,浏览器下载对应 ROM,校验内容,再交给 WebAssembly 模拟器载入。

单机时可以直接使用:

  • 键盘;
  • 标准 Gamepad API 手柄;
  • 用户主动授权的 WebHID 兼容手柄;
  • 移动设备上的触控按键;
  • 三个即时存档槽;
  • SRAM 自动保存;
  • 本地选择的 .nes 文件。

本地双人也可以共用一台电脑。P1 使用 WASD、J/K、Shift 和 Enter,P2 使用方向键和小键盘。

不过这个项目里更有意思的部分,是把原来的本地双人搬到两台浏览器上。

一条链接建立双人房间

房主先载入游戏,再创建联机房间。页面会生成房间码和邀请链接,链接里带有游戏 ID 与房间码。

朋友打开链接后,页面自动找到同一款游戏并加入房间。双方会再次检查 ROM SHA-256 和模拟器核心版本,只有完全一致才继续建立游戏通道。

Game 双人体验流程:房主选择游戏并创建房间,把邀请链接发给朋友,双方校验相同 ROM 与核心版本,建立输入、控制和音视频通道后开始游戏
使用者看到的是一条邀请链接;底下会完成游戏载入、版本校验、RTC 建连和初始状态同步。

连接完成后,角色是固定的:

  • 房主控制 P1;
  • 客机控制 P2。

游戏运行在两边本地。网络传输的是按键和必要的状态数据,不是每一帧画面。这样在连接正常时,操作不需要先绕到服务器再返回。

可以边聊天边玩

进入房间后,麦克风和摄像头默认不会擅自打开。用户可以分别启用语音和视频,浏览器会在操作时请求设备权限。

音视频与游戏数据使用同一个 RTCPeerConnection,但彼此不是同一个业务通道:

  • 音频轨道传麦克风;
  • 视频轨道传摄像头;
  • input DataChannel 传逐帧手柄输入;
  • control DataChannel 传握手、状态和同步控制。

摄像头打不开,不会让游戏跟着断开;麦克风授权失败,也不影响两条 DataChannel。对这类怀旧游戏来说,视频不是必需品,但看到对方打到熟悉场景时的反应,会比纯粹联机多一点意思。

网络抖动时的回滚与同步

如果每一帧都等待远端按键,网络延迟会直接变成操作延迟。当前实现会让本地先运行,暂时预测对方输入;真实输入到达后,如果预测不一致,就恢复最近的 checkpoint 并重新执行几帧。

双方还会定期比较模拟器状态 hash。发现状态不一致、回滚窗口不够,或者 RTC 连接刚刚重建时,游戏会暂停,由房主发送完整状态,把客机重新拉到同一帧。

使用者看到的应该只是短暂的“正在同步”,而不是自己处理存档和进度。具体实现分别记录在:

联机要求 ROM 与核心版本完全一致

两边显示相同游戏名,不代表 ROM 内容一定相同。翻译版、修改版和不同归档中的同名文件,都可能有不同字节内容。

项目不按文件名判断联机兼容,而是核对 ROM SHA-256。模拟器核心源码发生变化后,coreBuildId 也会改变。只要 ROM 或核心版本不同,双方就不能继续同步。

这项限制看起来严格,但它避免了更麻烦的情况:两边勉强开始后,在某个关卡因为代码或 Mapper 行为不同而悄悄跑偏。

项目目前支持 16 个 Mapper 编号。卡带如何切换 PRG、CHR bank,以及 MMC1、MMC3 和其他 IRQ 型 Mapper 怎么适配,单独记录在 Mapper 适配记录:从 Bank Switching 到 IRQ

目前还不是一个公共游戏平台

当前站点是受控访问的自托管项目,不是面向公众分发 ROM 的开放平台。

现有目录有 476 个内容不同的 ROM,它们通过了当前的启动、存档恢复、确定性重放和帧哈希回归。这只能说明测试过的路径可以运行,不能证明每款游戏都已经人工通关,也不能把模拟器代码的 MIT License 延伸成游戏内容许可。

联机体验也还有明确边界:

  • 只支持两个人;
  • 房主固定为 P1;
  • 还没有覆盖所有浏览器与网络组合;
  • 弱网下可能出现回滚、暂停或完整状态重同步;
  • 浏览器进入后台后会暂停联机;
  • WebHID 在部分浏览器中不可用。

这些限制暂时都没有藏起来。项目现在更像一个已经能实际使用的私人游戏空间,而不是完成度很高的游戏平台。

最后

这个项目最开始的目标并不复杂:让一款老游戏重新变成两个人共同经历的事情。

打开网页,挑一款熟悉的游戏,把链接发给很久没见的朋友。语音里聊着近况,偶尔开一下摄像头,再为谁抢走道具、谁先掉下去笑一会儿。

技术上,底下有模拟器、Mapper、WebAssembly、WebRTC、回滚和状态同步。真正使用时,希望它只剩下一件很轻松的事:今晚要不要再打一局。