osu-droid难度算法实现剖析:Aim、Speed与Reading技能模型深度解析
osu-droid难度算法实现剖析:Aim、Speed与Reading技能模型深度解析
【免费下载链接】osu-droid项目地址: https://gitcode.com/gh_mirrors/os/osu-droid
osu-droid 是开源社区广受欢迎的 osu! Android 移植版,其自研的难度算法堪称评价谱面难度与玩家水平的"大脑":通过 Aim、Speed、Reading 等技能模型,把一张包含数百个音符的谱面,压缩成一个直观的星数与对应的 pp 值。这篇难度算法深度解析将从源码视角,一步步拆解这套技能模型的计算流程与设计思想。
一、难度算法整体架构:一条从谱面到星数的流水线
osu-droid 的难度计算入口是DroidDifficultyCalculator,它遵循经典的 osu! 难度算法三阶段流程:
- 预处理:把谱面物件包装成
DroidDifficultyHitObject,归一化距离、计算时间间隔与角度。 - 技能建模:创建多套"技能"并行打分,每套技能独立评估谱面的某一维度负担。
- 汇总换算:把技能得分加权组合成 base performance,再开立方得到最终星数(Star Rating)。
其中技能创建逻辑非常直观(createSkills):默认创建DroidAim(带滑条与不带滑条两份)、DroidRhythm、DroidTap;开启 Flashlight 模组时追加DroidFlashlight;DroidReading则始终参与计算。最终星数公式为:
starRating = cbrt(basePerformance * FINAL_MULTIPLIER)其中basePerformance由 Aim、Tap、Cognition(Reading 与 Flashlight 的合成)通过范数(norm)加权得到。相关源码位于src/com/osudroid/difficulty/calculator/DroidDifficultyCalculator.kt。
二、Aim技能模型:把"瞄准"拆成 Snap、Agility 与 Flow
Aim 技能(DroidAim)是整个难度算法中最复杂的模块,它继承自可感知滑条长度的VariableLengthStrainSkill,将"瞄准"拆成三个正交维度:
- Snap(点按瞄准):评估相邻物件间的跳跃距离与角度,
DroidSnapAimEvaluator会奖励锐角、广角与快速转向,并对重复角度做削弱处理。 - Agility(敏捷):评估光标速度的突变率,衡量连续快速转向的负担。
- Flow(顺滑):评估流线型连续滑动的难度,主要作用于串(stream)段落。
三个维度分别乘以 70.9、2.35、242 的权重后,再通过 logistic 函数计算"该音符更偏向 Snap 还是 Flow"的概率,加权求和得到当前物件的难度值。此外,Aim 还会记录每个滑条的速度(travelDistance / travelTime),用于统计"困难滑条"占比,这与结算面板上的滑条系数直接相关。
每个技能都有一套应变衰减模型,Aim 的衰减系数为0.2^(ms/1000),即 1 秒内难度记忆衰减到 20%;同时它会将前 4 秒内的最高应变按 20ms 分块削减,避免极端难度尖峰过度抬高整张谱面的星数。核心实现见src/com/osudroid/difficulty/skills/DroidAim.kt。
三、Speed(Tap)技能模型:BPM、节奏与三指检测
Speed 技能在 osu-droid 中名为DroidTap,属于谐波求和类技能(HarmonicSkill),衡量玩家跟上击打速度的能力。其评估器DroidTapEvaluator的核心公式非常简洁:
strain = speedBonus * 1000 / strainTime- BPM 加成:当音符间隔对应的 BPM 超过 200 时(
MIN_SPEED_BONUS = 200),通过误差函数逐步放大难度,200 BPM 以上每提速一点都极其"昂贵"。 - 连点削权:利用
doubletapness(连点程度)识别可以被"双指连打"糊过去的段落并降低其难度,这就是常说的反作弊(cheesability)设计。 - 节奏调制:每个音符的 Tap 难度还会乘上
DroidRhythm提供的节奏倍率,节奏越难(如变速、切分音)倍率越高。
值得一提的是,osu-droid 还内置了三指速段检测:当 Tap 应变连续超过阈值 175 且物件数不少于 5 个时,会标记为"可能三指速打"的段落(possibleThreeFingeredSections),这一信息会进入 pp 计算用于识别移动端特有的作弊手段。相关实现见src/com/osudroid/difficulty/skills/DroidTap.kt与DroidTapEvaluator.kt。
四、Reading技能模型:信息密度与 AR 的阅读负担
Reading 技能(DroidReading)是 osu-droid 相对原版 osu! 的特色模块,专门衡量"读图"难度,其评估器DroidReadingEvaluator在 3000ms 的"阅读窗口"内综合四类负担:
- 音符密度:统计当前物件出现时与需要点击时可见的物件数量,密度越高越难读。
- AR 预判:以 AR 9.66(500ms preempt)为起点,approach rate 越高、反应时间越短,preempt 难度呈 2.5 次方增长。
- Hidden 加成:开启 Hidden 模组时按 0.28 系数放大阅读难度,并额外奖励"完美堆叠"(前一个音符点击时当前音符完全不可见)的段落。
- 恒定角度削弱:检测相似角度的反复出现,连续同角度的物件会显著降低阅读难度(熟能生巧)。
此外,Reading 还模拟了人类"前几秒背谱"的行为:谱面前 60 秒内的音符难度会被按对数曲线削减(applyDifficultyTransformation),这与我们"第一遍打谱难、越打越简单"的直觉完全吻合。核心实现见src/com/osudroid/difficulty/evaluators/DroidReadingEvaluator.kt。
五、汇总换算:技能值如何变成星数与 PP
当各技能完成打分后,DroidDifficultyAttributes会收集 Aim、Tap、Rhythm、Flashlight、Reading 五类难度值,以及"困难应变数""困难滑条数""可能三指段"等辅助统计量。随后:
- 难度 → 性能:
HarmonicSkill.difficultyToPerformance采用4 × difficulty³的换算(StrainSkill 类也有对应的换算函数),把技能值映射为性能值。 - 性能 → 认知合成:Reading 与 Flashlight 先合成
cognition(光源对读图的影响按比例压缩)。 - 最终星数:Aim、Tap、Cognition 三者通过 norm 组合后再开立方,得到与玩家直觉高度一致的星数;这个星数连同难度属性一起喂给
DroidPerformanceCalculator,最终算出 pp。
整套换算链条都封装在src/com/osudroid/difficulty/attributes/与src/com/osudroid/difficulty/calculator/目录下,属性类与计算器分离的设计让二次开发十分清爽。
六、如何自行调试与二次开发这套难度算法
如果你也想动手研究这套难度算法,可以在本地克隆仓库后直接阅读与修改:
git clone https://gitcode.com/gh_mirrors/os/osu-droid推荐三个探索入口:
- 实时调试:
src/com/osudroid/debug/DebugPlaygroundScene.kt提供了难度计算的调试场景,可直观看到各类技能值的变化。 - 批处理入口:
src/com/osudroid/beatmaps/DifficultyCalculationManager.kt负责对谱面库批量计算难度。 - 单元测试:
tests/test/kotlin/com/osudroid/下保留了 83 个测试文件,覆盖了技能评估器与换算公式的边界情况,修改算法后跑一遍测试即可验证效果。
对于想给 osu-droid 贡献新模组难度加成、或复刻这套移动端难度算法的开发者来说,从 Aim 的 Snap/Flow 概率模型与 Reading 的密度窗口这两个最有"移动端特色"的部分入手,是最快理解整套设计精髓的路径。
结语
通过这次对 osu-droid 难度算法的剖析可以看到,它并非简单照搬桌面版 osu!,而是在 Aim 中引入 Snap/Agility/Flow 三维拆分、在 Speed 中加入三指检测、在 Reading 中构建信息密度窗口,处处体现着对"手指触屏操作"这一场景的针对性建模。理解这套技能模型,不仅能帮你读懂星数与 pp 背后的逻辑,更能为移动端音游的难度设计提供一份高质量的参考样本。
【免费下载链接】osu-droid项目地址: https://gitcode.com/gh_mirrors/os/osu-droid
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
