A Dance of Fire and Ice(冰与火之舞)的 4K 下落式辅助读谱 mod。 直接读取游戏内存里已经构建好的谱面,渲染成 4 条下落轨道,并用三种算法自动排键。
⚠️ 本 mod 旨在帮助理解谱面配置,或者提供一种玩法。请不要只是为了直接打出更好的成绩而使用它 —— 某种意义上这是一种外挂。
面向 ADOFAI 3.3.1 / Unity 6000.3.10f1 / Mono / UnityModManager 0.32.4。当前版本 1.1.1。
同一张谱,三种算法给出三种完全不同的手序。它们没有绝对的好坏,只有取舍;面板「排键(手序)」里 点「手法」按钮循环切换。1.0.1 起默认是「时间重置法」。
| 手法 | 依据什么 | 优势 | 劣势 |
|---|---|---|---|
| AdoFalling 原版 | 相邻音符的间隔(RollWindowMs) |
简单、和原版 mod 一致 | 密集段整条手序就是楼梯;处理不了双押(同时的两颗音只能挤在一起) |
| 拆手序(移植自 ReADOFAIMacro,MIT) | 谱面 BPM + 节拍(180° = 一个窗口) | 手序非常合理:强拍给主手、弱拍给副手;双押和轮指都能处理 | 三连音交互会很灾难:窗口按 180° 切,三条 1/3 拍的音只在归一化缩放正好为 1 时才凑成一个窗口,其余情况边界落进音组中间、还把组后面那颗音一起吞掉,整段手序从此错位一位;依赖原曲 BPM |
| 时间重置法(默认,1.0.0 新增) | 只有音符间隔(一个参数:主手重置间隔) | 和谱面 BPM 完全无关,只有「换手时间」的概念,各种奇怪节拍/变速都能处理;足够快的三连音锁成 JKF 循环 |
每次一定从主手开始;不看强弱拍、不考虑节奏,只考虑怎么让手指舒服 |
间隔小于「双押合并」的两颗音当作一次双押:后一颗被移到和前一颗同一时刻,两颗画在同一行上。
默认 50 ms,0 = 关。它只改「看起来是什么谱」,不改手指怎么分,所以三种手法都套用;
而且只移按键 —— 长条的尾音是同一个键的松手,必须留在长条末端(同理也不会弄坏任何长条的长度)。
实测(R = 165ms、merge = 50ms,写成 时刻+手指,单位 ms):
| 音型 | 合并后 |
|---|---|
50+400 |
0J 0K 450J 450K 900J 900K …(5 处合并) |
40+40+40 |
0J 0K 80F 80D 160J 160K …(5 处合并) |
100+100+200 |
不变(间隔 100/200ms > 50ms) |
面板「排键(手序)」里还有一个**「刷新手序」按钮:改了设置或换了谱面后发现手序没更新就点它。 每次重算都会先把音符时间还原成谱面自己的**再排,所以点几次结果都一样(不会被上一次合并过的 时间反过来影响)。
只有 R = 主手重置间隔(默认 165ms)一个参数。状态 = 上一个音用的手指
(D 副轮 / F 副手 / J 主手 / K 主轮),判据全部含等号:
| 上次手指 | 判据 | 本次 |
|---|---|---|
| 无 / 副轮 | — | J |
| 主手 | d > R |
J(慢:一根指头能一直点) |
| 主手 | R/2 < d ≤ R |
F(强拍主、弱拍副) |
| 主手 | d ≤ R/2 |
K(一只手来不及,同手轮指) |
| 主轮 | d > R |
J(快速段结束,回落) |
| 主轮 | d ≤ R |
F(还在快速段里,换手) |
| 副手 | dOff ≤ R/2 或 dMain ≤ R |
D(副手也得轮) |
| 副手 | 否则 | J |
d = 本次 − 上一个音(「主轮」那一行比的就是主轮那一下本身,不是它回应过的那颗主手音;
长按挡在中间时「上一个音」是尾音,手指松手才算空出来);dOff/dMain = 上次副手/主手音。
外轮是收尾的一遍扫描:每个 K/D 和它前一个按键交换手指 ⇒ 外侧指先落(K J D F)。
长条的尾音永远跟在头的轨道上,并且参照时间推进到尾音(手指松手才空出来),链式长条整段同轨。
离线实测(tools/TimeResetCheck.cs,R = 165ms):
| 均匀间隔 g | 周期 | 内轮 | 外轮 |
|---|---|---|---|
g > R(>165ms) |
1 | J J J… |
同 |
R/2 < g ≤ R(83~165ms) |
2 | J F J F |
同 |
g ≤ R/2(≤82ms) |
4 | J K F D |
K J D F |
音型的实测(同样 R = 165ms):
| 音型 (ms) | 内轮 | 外轮 |
|---|---|---|
| 50+50+100(更快) | JKF 循环 |
KJF 循环 |
| 80+150 | JKFD 循环 |
KJDF 循环 |
| 80+220 | JKJK 循环 |
KJKJ 循环 |
| 100+100+200 | JFJ 循环 |
JFJ 循环 |
| 100+100+300 | JFJ 循环 |
JFJ 循环 |
| 125+125+250 | JFJ 循环 |
JFJ 循环 |
| 200+200+400 | J 连点 |
J 连点 |
| 100×4(等分) | JFJF |
JFJF |
| 50+400 | JKJK(副手全程不动) |
KJKJ |
三连音要锁成
JKF,需要组内 ≤ R/2、组间 > R/2:R = 165ms 时锁得住的是50+50+100这种更快的三连音;100+100+200在这一档是JFJ(想把它也锁住,得把 R 调到 200ms 以上)。 最后一行要知道:主轮之后d > R就回落主手,所以「快双击再隔 400ms」这种音型副手一直闲着。 想让它换手,把 R 调大到 400 以上。
| 功能 | 说明 |
|---|---|
| 实时读谱 | 从游戏内存读已构建的谱面,不需要谱面文件,兼容任意自制谱 |
| 4K 视图 | 4 条轨道 / 判定线 / 判定框 / 键位标签 D F J K |
| 三套手序 | 「AdoFalling 原版」「拆手序」「时间重置法」(默认)可切换,见上。中旋在读数时就被折算成一颗普通砖(不是被丢掉),再和别的音符一起进手序 |
| 多语言面板 | 中文内置(代码里的中文原文就是 key);英文在 lang\en.txt,复制它只翻译右列就能加任意语言。面板顶部按钮切换,首次运行跟随游戏语言 |
| 双押合并 | 三种手法共用:间隔小于阈值的两颗音当作一次双押,后一颗移到和前一颗同一时刻(同一行),默认 50ms,可关 |
| 打击弹跳 | 音符到线时的弹跳反馈,纯时间驱动,不读游戏判定 |
| 暂停保留 | 暂停时叠加层留在屏幕上、音符停在原地;「按任意键开始」那屏只有静态框 |
| 小节线 | 每拍细线、每小节粗线;节拍按 TBPM 铺,跟着谱面的加速/减速变化 |
| 轨道长度可定 | 独立设置(默认 1620px = 1.5 屏,0 = 一直画到屏幕顶);音符和小节线也从轨道顶端开始出现,所以它同时决定你有多久的反应时间 |
| 调色板 | 轨道底色/边框、音符/音符边框、判定线、判定框、键位标签、细小节线/粗小节线各自带 RGBA;轨道与音符可「四轨单独配置」或一键统一 |
| 主题预设 | 七个一键套用:经典蓝橙、亮底黑线、霓虹紫青、黄金黑底、四轨彩虹、极简纯白、纯黑底经典白线 |
| 全屏纯色背景 | 默认关;打开后用一整屏纯色盖住游戏原本的画面,默认黑色 50%,颜色和透明度都可调 |
| 面板 | 分区可折叠(标题是 ▶/▼ 按钮,不是开关圆圈);分区按层级缩进;每个滑块都能直接填数字;颜色行支持 RRGGBBAA;设置之间留空行;面板顶部写明三种手法各自的优势与劣势(只显示当前那种);「排键(手序)」里有**「刷新手序」**按钮 |
| 实验性平滑下落 | 默认关;沿轨道方向的淡拖尾(有自定义图片时拖尾也用那张图),长度是固定的毫秒窗,不随帧时间忽长忽短 |
| 实验性补帧 | 默认关;一帧把叠加层画三次(上一帧 / 下一帧 / 当前帧),只重画会动的那一半 |
| 实验性 3D 轨道 | 默认关;针孔相机:灭点在轨道顶端,音符是贴在轨道上的板;轨道体是一叠内切横带拼的,把「轨道边框粗细」调粗就能盖住台阶 |
| 长按 | 默认开;读 holdLength 画长条,末端始终接住下一颗音符;开头过线后被判定线慢慢吃掉;尾巴那颗音符强制与开头同轨 |
| 自定义音符图片 | 用 png/jpg(含 alpha)替换音符;有图时就只画那张图;长按线条可另指定一张 |
| 娱乐(实验性) | 默认关;轨道乱飞 / 持续旋转 / 四花瓣 —— 纯好玩,不影响读谱 |
| 整体旋转 | 0~360°,整个叠加层绕判定线中点旋转 |
| 诊断日志 | 可关闭;排键、拍网格、每帧门控都有一行可对照的日志(时间重置法会打出前 32 个音的手指序列,方便调 R) |
| 显示帧数 | 默认关;打开后在判定线下方显示 fps + 帧间隔(ms) |
从 Releases 下载 LaneReader-<版本>.zip,解压出来的就是 Mods\LaneReader\
(LaneReader.dll + Info.json + lang\ + 示例图片 + 安装说明),整个文件夹丢进
<游戏目录>\Mods\ 即可。手动装法一样:
- 把
LaneReader.dll、Info.json、lang\放进<游戏目录>\Mods\LaneReader\ - 启动游戏,UMM 的 mod 列表里会出现
LaneReader (4K view)
装之前必须完全退出游戏(dll 会被占用)。
设置面板在 UMM 的 mod 设置里(全部手写 GUILayout,不依赖 UMM 的自动绘制
—— 那套在这台安装上对自绘面板的 mod 无效)。
Set-Location LaneReader
powershell -ExecutionPolicy Bypass -File .\build.ps1 # 只编译
powershell -ExecutionPolicy Bypass -File .\build.ps1 -Install # 编译并部署到游戏- 用
csc.exe(.NET Framework 4.0.30319)编译,/langversion:5(不能用 C# 6+ 语法) - 部署前必须完全退出游戏,否则 DLL 被占用(
user-mapped section open) - 每次编译也顺手打出 release 包
LaneReader/build/LaneReader-<版本>.zip(根目录是LaneReader\, 和 UMM 的安装结构一致,版本号取自Info.json) - 构建产物
LaneReader/build/不入库
| 设置 | 默认 | 说明 |
|---|---|---|
| 流速 | 1000 px/s |
1080p 基准像素,实际按屏幕高度缩放 |
| 手法 | 时间重置法 | 可切「拆手序」或「AdoFalling 原版」 |
| 主手重置间隔 | 165 ms |
时间重置法唯一的参数。150 BPM 下:8 分音(200ms)=单指、16 分音(100ms)=双手交替、≤82ms=轮指 |
| 双押合并 | 50 ms |
三种手法共用:间隔小于它的两颗音当双押,后一颗移到和前一颗同一行;0 = 关 |
| 最大硬抗 BPM | 850 |
拆手序的标定旋钮:超过它就把窗口减半一次 |
| 轮指方向 | 外轮 K J D F |
拆手序与时间重置法共用,可切内轮 J K F D |
| 轨道长度 | 1620 px(= 1.5 屏) |
固定长度;0 = 一直画到屏幕顶端 |
| 判定线 | 显示、横跨全屏 | 开关和长度都在「布局」里 |
| 长按支持 | 开 | 长条 = 从开头音符一直连到下一颗;没长按的谱完全不受影响 |
| 全屏纯色背景 | 关 | 开了就是黑色 50% |
| 音符边框 / 小节线 | 开 | 可关 |
| 自定义音符图片 / 整体旋转 | 关 / 0° |
不影响原来的外观 |
| 颜色 | 经典蓝橙(暗底白线) | 可在「外观 / 颜色」里逐项改或换主题(共 7 个预设) |
| 平滑下落(实验) | 关 | 开了才有拖尾;长度 40ms、层数 8、浓度 0.45 |
| 补帧(实验) | 关 | 一帧画三次,暂停不叠 |
| 轨道边框粗细 | 3 px |
轨道两侧那条线;3D 下同时是抗锯齿的手段 |
| 长按图片重复间隔 | 1 拍 |
每拍把 hold.png 首尾相接一次;0 = 整条只贴一张 |
已经存过
Settings.json的机器会继续使用自己保存的值,不会套用新默认。 想用新默认:面板「其它 → 恢复默认」再「保存」,或删掉Mods\LaneReader\Settings.json。关于「最大硬抗 BPM = 850」:本机读不到谱面 BPM 时,拆手序会落到自己的兜底 800 BPM。 上限设成
850时 800 不会被减半,base = 800;旧的506会把它减半成 base = 400。 base 越大,一个窗口里塞得下的音符越少,轮指就越少、换手也越少 —— 这是手感差异的来源。
把图片(png / jpg)放到 Mods\LaneReader\,面板「显示 → 用自定义音符图片」打开,
路径默认 note.png(相对路径基于 mod 目录,也可以填绝对路径)。
安装目录里带了一张演示图 note.png(圆角胶囊、半透明填充),可以直接开来试。
- png 的 alpha 生效;jpg 没有 alpha
- 有图时就只画那张图:不画原来的音符色块,也不画音符边框
- 「保持宽高比」默认开(contain 居中,按平面音符框算,所以透视下也是这个样子被投影)
- 「长按线条用自定义图片」是另一张图(默认
hold.png):图片下端 = 长按开头, 上端 = 尾巴;「重复间隔」默认 1 拍 = 每拍把图片首尾相接一次,份数按长按自己的拍数 四舍五入成整数(和下落速度无关)。判定线从下面吃、尾巴从上面切,两端都是裁掉 - 换图不用重启:最多 1 秒后自动重读,面板上还有「重新加载」按钮
面板支持多语言。中文是内置的 —— 代码里的中文原文就是 key,不需要任何语言文件;英文在
<游戏目录>\Mods\LaneReader\lang\en.txt。面板顶部有语言按钮,点它就在 lang\ 里找到的语言之间循环。
加一种语言:把 lang\en.txt 复制成 lang\jp.txt,左列一个字都别改(那是 key,
连行首的空格也算),把右列翻译掉,存成 UTF-8 —— 面板按钮重新扫目录,下一次点就能选到它,
不用重启。
- 格式:每行
原文<TAB>译文,用制表符分隔。之所以不用=:不少原文里本身就有=(轨道长度 (px, 0=自动到屏幕顶)、间隔小于窗口 = 轮指;大于 = 食指交替。)。不含=的 key 也可以写成原文 = 译文,手写更舒服。 #或;开头是注释;值里的\n表示换行;行首空格是面板的缩进,属于 key。开/关、四轨单独/四轨统一、主题名、手法名这些都要翻译(列表就在en.txt里)。- 语言存在
Settings.json的Language;留空(新装的默认)= 跟随游戏自己的语言 (非中文一律先落到英文)。某个语言没有文件时回落中文,不会变成空白。 - 安装时
lang\*.txt只在缺失时才复制,所以你改过的译文不会被覆盖; 想拿回仓库里那份,删掉文件再装一次。
-
HANDOFF.md:完整技术交接文档 —— 架构、每个踩过的坑、调试方法、门控规则。 三种手法的推导分别在 §14(拆手序)、§18(时间重置法)、§6.7.1(「度」标尺的更正) -
LaneReader/tools/HandPlanCheck.cs:拆手序的离线 oracle, 把移植版与参考实现逐窗口比对(28 张谱面 / 17558 个窗口,决策与指法符号完全一致) -
LaneReader/tools/TimeResetCheck.cs:时间重置法的离线校验, 与游戏无关,编译只要两个源文件:cd LaneReader\tools csc /nologo /target:exe /langversion:5 /out:TimeResetCheck.exe TimeResetCheck.cs ` ..\src\LaneReader\TimeResetPlanner.cs ..\src\LaneReader\FingeringPlanner.cs ` ..\src\LaneReader\DoublePressMerge.cs .\TimeResetCheck.exe 165 # 内置表:均匀间隔 / 音型 / 长条 / 双押合并(不填按 300) .\TimeResetCheck.exe <dump> 165 [inner] # 参考宏 dump 整首走一遍
1.1.1
- 面板顶部补一行说明:叠加层只在关卡里出现,菜单、选歌界面和设置面板里看不到(有不少人以为没生效);
下面再补一行**「叠加层状态」**,直接显示不画的原因(
waiting: scrLevelMaker.instance is null等) - 新增兜底驱动(
OverlayWatchdog.cs):叠加层原本只靠AsyncInputManager.Update/OnGUI两个补丁,而 MonoBehaviour 所在的 GameObject 不激活就收不到回调 —— 那会让 mod 变成「面板能开、 叠加层永远不画、日志一行都没有」。现在 mod 自己创建一个DontDestroyOnLoad的 GameObject, 游戏那两个钩子安静 0.5 秒后由它接管(正常机器上完全不动);接管时把GUI.depth设到游戏 UI 之上 - 新增排查日志:
driver: game hooks alive/never fired/stopped firing、overlay suppressed: Enabled=… hidden=…、idle: <原因>、chart reader disabled: <原因> - 排查方法记在 HANDOFF §21
1.1.0
- 面板多语言(
Localization.cs):中文内置(代码里的中文原文就是 key),英文在lang\en.txt; 复制它、只翻译右列就能加任意语言;面板顶部按钮切换,语言文件缺失时回落中文 - 首次运行跟随游戏语言(非中文 → 英文),选择记在
Settings.json的Language - 安装时
lang\*.txt只在缺失时复制,改过的译文不会被覆盖
1.0.3
- 新增双押合并(
DoublePressMerge.cs,MergeRollMs默认50 ms,面板放在「排键(手序)」的 公共位置):间隔小于它的两颗音当作双押,后一颗移到和前一颗同一时刻;三种手法共用, 只移按键,长条尾巴不动;0= 关 - 「排键(手序)」里新增**「刷新手序」**按钮(手序没刷新时点一下);每次重算前会把音符时间 还原成谱面自己的,所以反复点结果稳定
- 面板的手序介绍改成只显示当前模式;设置行之间加空行
1.0.2
- 主轮之后的判定改看「上一个音」(主轮那一下本身)的间隔,不再回看它回应过的那颗主手音: 一次快速双击后隔 150ms 的音现在仍然换手(旧规则按「距上上个音 230ms」会判回主手); 长条头落在主轮时同理 —— 从尾音(松手)那一刻起算,长条后那颗音因此从 J/K 变成 F/D
1.0.1
- 默认手法改为时间重置法(
FingeringMode默认 2) - 主手重置间隔默认
300 ms→165 ms:刚好卡在 150 BPM 的 8 分音(200ms)之下 —— 8 分音用单指连点、16 分音(100ms)双手交替、更密的才进轮指
1.0.0
- 新增第三种手法「时间重置法」:只看音符间隔的排键状态机,一个参数,不读 BPM/角度、不分组,
正面解决「三连音被
2^k缩放切错、边界落进音组中间」的问题 - 外轮收尾扫描、长条尾音永远同轨(链式长条整段同轨)、边界加 1ns 容差
(时间是 double,间隔正好等于 R 或 R/2 时
0.45-0.3 = 0.15000000000000002会让判定逐音翻面) - 面板写明三种手法各自的优势与劣势;README 重写
本项目以 MIT 发布,见 LICENSE。
排键算法移植自他人项目,版权与来源见 THIRD-PARTY.md:
ReADOFAIMacro(MIT)与
AdoFalling。