ORBIT:把 osu! 整张谱面的读谱预览搬进浏览器
0x9D5F8893
玩 osu! 时,我经常有一个很小、但一直没被完全满足的需求:拿到一张谱面的 ID 后,不急着进游戏,先看一遍它到底怎么打。
曲名、封面、星级和几项难度数值只能告诉我一部分信息。连续跳跃的方向、滑条什么时候反向、密集段落如何衔接,还是得放到时间轴里才能看清楚。尤其当我想反复研究十几秒的片段,或者比较加速前后的读谱压力时,完整的播放、暂停、跳转和循环,比一张静态介绍卡片更有用。
于是我与 AI 协作,把这个想法做成了 ORBIT:一个在浏览器里运行的 osu!standard 整谱 Auto 预览工具。本文介绍的是已经跑起来的本地版本,不是一个已经开放给所有人全天候使用的在线服务,也不是未来功能清单的包装。
项目地址:zureealLV / ORBIT。仓库首页默认中文,另有英文说明。
先看界面:输入数字,不是直接盲下谱面

搜索框支持单难度 ID、谱面集 ID 和 osu! 官网链接。输入后停顿约三百五十毫秒,它先查询公共元数据,在搜索框下显示封面、曲名、曲师、谱师、难度数量、星级范围和基本时长信息。这个阶段只查信息,不下载整包。
最开始我把每个难度都列成一张卡片,结果一首曲子有十四个难度,就把同一张封面重复了十四遍。信息确实齐全,使用体验却很笨。现在按谱面集聚合,同一个集合只显示一条;载入之后,再在右侧切换同集难度。列表是一列,展开和卡片淡入有轻量动画,同时遵循系统的减少动态效果偏好。
这里也要分清两种 ID:320118 是示例 No title 的谱面集,738063 是其中一个具体难度。输入后者时,卡片虽然按谱面集合并,点击目标仍是这个难度,不会因为同集有更高星级就擅自换掉。数字查询是完整 ID 查找,不是按任意数字前缀检索全站。不同谱面集恰好分别匹配同一个查询数字时,也不会因为曲名相似就被错误合并。
我想保留的是读谱工作流,而不是一个播放按钮
进入播放器之后,核心操作都围着谱面画面展开。圆圈、滑条、转盘和 Auto 光标是主角,设置收在「读谱」「皮肤」「声画」三个页签里。深色底与少量粉色只是辅助,不再用大段介绍占据读谱空间。
下方的物件密度时间轴可以帮助定位段落,也可以直接拖动跳转。A、B 两个标记用于反复看同一段:先定位起点,再设终点,不需要每次回到歌曲开头。练习倍率可以独立调整,和 MOD 带来的速度变化共同作用;显示的 AR、OD 与 BPM 是当前速度下的读谱参考,不是在浏览器里重算完整难度星级。
键盘也保留了简单入口:空格播放或暂停,左右方向键跳转五秒,方括号设定循环起止。输入框聚焦时不会抢这些按键,避免正在输入 ID 却把播放器启动起来。
这些功能看起来不如“支持所有 MOD”醒目,但我更在意它们能否连成一条顺手的路径:找到谱面,看懂它,在难点处停下,再带着明确的问题回到游戏。
不是套一个播放器:从文件解析到画面合成
ORBIT 的主要实现是 TypeScript、Canvas 2D、Web Workers 和 Web Audio。Node 负责本机资源层,fflate 负责解压 ZIP;谱面解析、滑条几何、Auto 轨迹、渲染与音效调度则由项目自己实现,没有嵌入此前研究过的第三方 Rust/WASM/WebGPU 预览引擎。
ID / 官网链接 → 元数据查询 → 点选后下载完整 .osz本地 .osz ───────────────────────────┐ ↓ Worker 解包、解析 .osu / .osb ↓ 滑条几何、时序、Auto 轨迹 ↓ 音乐主时钟驱动 Canvas 与视频这条路线的优点是实现集中、容易阅读与修改,不要求浏览器提供 WebGPU。但它不天然意味着更快,也不意味着更接近游戏客户端。真正的密集场景性能、不同设备上的帧稳定性,都需要独立测量,不能因为技术栈轻就凭感觉下结论。
滑条也不只是沿几个点画一条线。直线、贝塞尔、圆弧和 Catmull-Rom 各有规则,控制点退化、声明长度的裁切与延长、红绿线带来的速度变化、反复端点和滑条 tick 都会影响最终结果。把这些计算放进 Worker,可以避免解包与几何处理直接阻塞界面;渲染侧再缓存轨道纹理,减少重复生成的工作。
皮肤:能读素材,不等于完整复制游戏客户端
我不想让预览永远停留在默认矢量圆圈,所以加入了本地 .osk 支持。它会读取游玩相关的贴图、数字、Combo 颜色、光标、部分动画与打击音,也允许切回项目自己的简约外观。
这里最容易产生误会的是滑条轨道:它不是从皮肤里取出一张万能的长条图片。轨道需要由引擎根据几何路径生成,边缘、透明度、渐入和末段收尾都需要独立处理。ORBIT 现在有渐变轨道、边框与阴影,以及可切换的 Snake-in / Snake-out,但还不能宣称与 stable 或 lazer 逐像素一致。
截图中使用的是本机安装的 Spoo 皮肤。仓库不会随源码分发第三方皮肤原包,新机器可以导入自己有权使用的皮肤,或者直接使用矢量外观。菜单、结算和完整游戏 HUD 的换肤,也不在当前支持范围内。
背景视频与故事板:先保证资源真的存在

这轮媒体适配里最有代表性的坑,不是某一行绘图代码,而是下载资源不完整。之前为了减小包体使用了 novideo 包;Hexen Noir 的旧缓存保留了故事板脚本,却缺少几乎全部贴图与视频。脚本解析得再正确,找不到素材仍然画不出来。
现在在线读取请求完整媒体包,并为它使用独立缓存键,避免继续命中旧的精简包。实际核验的 Hexen Noir 完整包约四十二兆字节,包含九千四百九十三个故事板精灵、五百三十八张引用图片,以及一段 MP4。这个数字说明的是这张谱面的资源规模,不是所有故事板都已经兼容的证明。
视频交给浏览器原生解码,保持静音,谱面音乐仍是主时钟。暂停、跳转和速度变化都从谱面时间同步到视频,避免各播各的。故事板则解析 .osu 内联事件和匹配的共享 .osb,按图层、时间与属性计算位置、缩放、旋转、颜色、透明度及循环。贴图按当前时段与短暂前瞻按需解码,不把整包图片一次性常驻为位图。
我在真实浏览器里核验了视频时间推进与暂停,也跳到五十七秒观察到与静态封面完全不同的故事板文字和场景。不过,条件触发器、故事板 Sample 音频、负时间片头暂时没有实现;视频编码若不被浏览器支持,也没有服务器转码来兜底。这些边界应当和截图一起写出来。
MOD 目录必须写状态,而不是只堆按钮

用户当然可以希望 stable 与 lazer 的 MOD 都能预览,但“有一个缩写按钮”和“完整实现游戏语义”不是一回事。当前目录共有四十八项,其中八项预览接入、二十一项近似效果、十四项说明项、五项明确不可用。具体覆盖表放在仓库文档中,不能把这个数量改写成“四十八项全部适配”。
例如,倍率和几何翻转能在 Auto 预览中直接体现;与真实输入、失误、失败和计分相关的规则,没有玩家判定系统就不可能得到完整复现。说明项不应伪造成绩,不可用项也不应偷偷启用一个看似差不多的效果。对一个练习辅助工具而言,告诉使用者哪里可信,和把可信的部分做好同样重要。
怎么运行,以及现在还不能做什么
git clone https://github.com/zureealLV/ORBIT.gitcd ORBITnpm cinpm run buildnpm start需要 Node.js 22.12 或更新版本;当前本机验证使用 Node.js 24.15.0。启动后打开 http://127.0.0.1:4173,Windows 也可双击仓库中的启动脚本。应用界面默认中文,英文读者可以查看英文 README。
本地 .osz 与 .osk 在浏览器里解析,没有文件上传接口。在线搜索会访问 Sayobot 元数据和 osu! 封面资源,点选后还需官方谱面数据与镜像下载;镜像速度、内容更新与第三方服务可用性不是 ORBIT 能保证的。音乐、美术、视频和皮肤的权利始终属于各自创作者。
当前有六十五项自动化测试和一次通过的本地生产构建,覆盖解析、几何、资源安全、时钟、搜索聚合与部分媒体行为;真实浏览器截图补充了运行证据,但不代替完整歌曲、跨浏览器或生产负载验收。
服务器目前只绑定本机地址,分享链接也仅在同一台机器上可用。开源到 GitHub 不等于已经部署上线。 如果以后向其他人提供全天候服务,还需要限流、缓存治理、监控、资源来源与内容合规评估,而不是把本机端口直接放到公网。
对我来说,ORBIT 的意义不是重新造一个完整 osu!,而是把一个小需求做成可以反复使用、可以继续迭代的工具:先看清楚,再上场。接下来值得投入的方向,是更系统的真实谱面回归、媒体兼容性和性能测量,而不是用更长的功能列表掩盖尚未完成的部分。
截图说明:Hexen Noir — ELFENSJóN,谱师 Ink;搜索结果 No title — Reol,谱师 VINXIS;游玩皮肤 BTMC X Spoo FREEDOM DiVE REiMAGINED,作者 Spoo。截图用于展示 ORBIT 的运行界面,第三方素材权利不因源码开源而改变。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!