概述

noctilink 以 .yzcpkg 包的形式分发歌曲,这是标准 ZIP 压缩格式,内部结构如下:

  • song.json:包级元数据与各难度索引
  • 每个难度一个 .yzc.json 文件(Stable / Drifting / Faint / Relink)
  • 一个音频文件(.mp3.ogg
  • 一个封面图文件(可选,.jpg.png

两种 JSON 格式当前版本均为 1song.json 与各 .yzc.json 中的路径相对于包根目录解析。


song.json

包级描述文件。由编辑器写入,游戏在导入时读取。

{
  "version": 1,
  "id": "mynick_mysong",
  "title": "Song Title",
  "artist": "Artist Name",
  "cover_color": [0.95, 0.70, 0.40],
  "audio": "music.ogg",
  "cover": "cover.jpg",
  "charts": [
    { "difficulty": "Stable",   "level": "5",  "chart_constant": 5.0,  "chart": "Stable.yzc.json" },
    { "difficulty": "Drifting", "level": "8",  "chart_constant": 8.0,  "chart": "Drifting.yzc.json" },
    { "difficulty": "Faint",    "level": "10", "chart_constant": 10.0, "chart": "Faint.yzc.json" },
    { "difficulty": "Relink",   "level": "12", "chart_constant": 12.0, "chart": "Relink.yzc.json" }
  ]
}

顶层字段

字段 类型 说明
version int 格式版本号(当前为 1
id string 歌曲唯一标识,需避开内置歌曲 ID 冲突;导入侧只接受小写英文字母、数字和下划线([a-z0-9_]
title string 显示用标题
artist string 艺术家或作曲者
cover_color array[3] 选曲列表中歌曲行的背景色,RGB 浮点值,范围 [0.0, 1.0]
audio string 音频文件名(包根目录相对路径)
cover string 封面图文件名,可选
charts array 难度索引(见下)

charts 条目字段

字段 类型 说明
difficulty string 必须为 "Stable""Drifting""Faint""Relink" 之一
level string 显示给玩家的难度等级,如 "7""9+""11"
chart_constant number 可选 — rating 用的谱面定数。缺省时,整数 level 视为 .0"9+" 这类整数加号视为 .5,非数字标签视为 0.0
difficulty_label string 可选 — 特殊谱面的显示标签覆盖;留空时使用 difficulty
chart string 难度对应的谱面文件路径
title string 可选 — 覆盖顶层 title
artist string 可选 — 覆盖顶层 artist
audio string 可选 — 该难度的独立音频覆盖
cover string 可选 — 该难度的独立封面覆盖
charter string 可选 — 该难度的制谱者署名
cover_source string 可选 — 封面来源说明(作者、链接、委托备注);在选曲详情面板与 charter 并列显示

仅在与顶层值不同的情况下才写入覆盖字段。


<难度名>.yzc.json

每个难度对应一个文件,描述元数据、拍号和音符内容。

{
  "version": 1,
  "metadata": { ... },
  "timing": { ... },
  "notes": [ ... ]
}

metadata

字段 类型 说明
title string 歌曲名
artist string 艺术家
charter string 制谱者
cover_source string 封面来源说明(作者、链接、委托备注);如 song.json 的 charts[] 条目里也写了会覆盖此处
difficulty_name string 必须与父 song.json 条目中的 difficulty 一致
difficulty_level string 难度等级,自由格式字符串
audio_file string 音频文件名(包根目录相对路径)
preview_time float 选曲界面试听起始点(秒)
preview_end_time float 试听结束点(秒),0 表示使用默认时长
background string 封面图文件名(可选)。名字虽叫 background,但它其实是封面图;游玩背景请用下面的 gameplay_background
gameplay_background string 可选 — 游玩时背景图文件名,与封面相互独立。设置后游玩界面会显示这张图(模糊/变暗处理与封面一致);为空时回退到模糊后的封面。与其他资源一样随包导出、相对谱面目录解析。

timing

字段 类型 说明
offset float 音频偏移量(秒);正值将谱面相对音频延迟(音频起始若有空白,使用正值把谱面零点推至音乐实际开始位置)
bpm_changes array BPM 变速点列表

每个 bpm_changes 条目:

字段 类型 说明
time float 新 BPM 生效的时间点(秒)
bpm float BPM 数值
beats_per_measure int 可选 — 编辑器时间轴拍号分子,默认 4
beat_unit int 可选 — 编辑器时间轴拍号分母,默认 4

beats_per_measurebeat_unit 只影响编辑器的小节线 / 标尺显示,不改变游戏内判定时序。

theme_events

可选的谱面主题切换数组。不使用该功能时会省略。

字段 类型 说明
time float 切换开始的谱面时间(秒)
target_t float 主题混合目标,0.0 = 暗色,1.0 = 亮色
transition_dur float 切换时长(秒),默认 0.6
force bool 可选 — 为 true 时强制应用该事件,即使玩家主题设置通常会覆盖谱面驱动主题

除数值 target_t 外,事件也可写 "mode": "dark""mode": "light",便于手改谱面(两者同时存在时以 target_t 为准)。transition_dur0 表示瞬间切换、无渐变。time0.0 的事件无论 transition_dur 为何都瞬切,以便在首帧绘制前确立谱面初始主题。若 theme_events 为空或缺省,引擎会在 0.0 秒插入一个隐含的暗色锚点(target_t 0.0),因此不使用该功能的谱面读作全暗。

approach_speed_events

可选的谱面内缩圈速度变更点数组。整首歌都使用普通 1.0x 缩圈运动时省略。 如果没有 0.0 秒事件,谱面等价于隐含存在 { "time": 0.0, "speed": 1.0 }

字段 类型 说明
time float 该缩圈速度开始生效的谱面时间点(秒)
speed float 非负缩圈运动倍率。0.0 表示暂停,1.0 表示正常速度,大于 1.0 表示加速

缩圈速度事件只影响音符本体/缩圈的出现时间与缩圈进度,不改变判定时间、BPM、音频时间、玩家 Ring Speed 设置,也不替代每个 note 自身的 ring_time_mult / ring_scale_mult;这些值会和谱面内缩圈速度事件复合计算。

notes

音符对象数组。建议按 time 升序排列,但非强制要求——引擎加载时会重新排序。


坐标系统

所有位置使用归一化坐标,范围 [0.0, 1.0]

  • x0.0 = 左边缘,1.0 = 右边缘
  • y0.0 = 上边缘,1.0 = 下边缘

该设计保证谱面跨分辨率一致。


音符类型

每个音符必有 typetimexy。特定类型附带额外字段。以下两个可选字段可出现在任意类型上:

字段 类型 说明
ring_time_mult float 提示圈持续时间倍率,1.0 = 使用全局值。等于 1.0 时省略
ring_scale_mult float 提示圈大小倍率,1.0 = 使用全局值。等于 1.0 时省略

Tap / Ex-Tap

标准单点音符。Ex-Tap 使用相同几何结构,不会出现 Pure+ 或 Break 以外的判定,并有视觉强调。

{ "type": "tap",    "time": 1.5, "x": 0.3, "y": 0.6 }
{ "type": "ex-tap", "time": 2.0, "x": 0.4, "y": 0.6 }

Hold / Ex-Hold

time 按下并持续至 end_time

{ "type": "hold",    "time": 2.0, "end_time": 3.5, "x": 0.5, "y": 0.5 }
{ "type": "ex-hold", "time": 4.0, "end_time": 5.0, "x": 0.5, "y": 0.5 }

Slide / Ex-Slide

在第一个路径点的时间按下,然后手指拖动slide 沿路径移动到终点。外层 timexy 与第一个路径点一致。

{
  "type": "slide",
  "time": 4.0, "x": 0.2, "y": 0.5,
  "path": [
    { "x": 0.2, "y": 0.5, "time": 4.0 },
    { "x": 0.5, "y": 0.3, "time": 4.5 },
    { "x": 0.8, "y": 0.5, "time": 5.0 }
  ]
}

中间节点与首末节点的区分按位置决定:index 0 为 head(画真节点圆点、进行 tap 判定、首节点 tap 音效),最末 index 为 tail(画真节点圆点、决定终点位置),其余 index 一律为中间节点(按倒角弧渲染——不画圆点、经过时不发出音效、不产生判定)。当前格式不再带 fake 字段;老谱面如果带了 fake 仍可正常加载(loader 静默忽略其值),编辑器重存后 fake 字段会从输出中消失。

Bezier 曲线段(可选):每个路径点还可以带 curve_in 字段描述该点的入边段形状——"line"(默认,省略不写)或 "bezier"。当 curve_in == "bezier" 时,必须配套写 cp_x / cp_y(二次 Bezier 的控制点,使用 chart 标准化坐标 0..1)。曲线公式:

P(t) = (1-t)² · P_prev + 2t(1-t) · CP + t² · P_this   ,   t ∈ [0, 1]

首节点(index 0)没有入边段,其 curve_in 在加载时被强制为 "line"。游戏侧和编辑器侧都用相同的 N_BEZIER_SAMPLES = 16 采样密度,distance / centerline 投影 / 渲染都基于该采样的折线近似。fillet(拐角圆滑)在曲线段两侧自动跳过——曲线本身已经平滑过拐角,重复处理会显得怪。

中间节点的 time 字段:仍然有意义——驱动轨迹上 guide light(指引亮点)沿轨迹的运动节奏。Guide light 在 path[k].time 时刻位于 path[k] 对应的渲染曲线位置,提供”现在应该划到哪里”的视觉参考。

on_time 字段(可选,默认 true:每个路径点可显式写 "on_time": false 表示「该点不参与 autoplay / guide-light 的时间锚定」。当为 false,guide light 与编辑器预览体不会在该点的 time 锚定位置;而是把周围相邻的 on-time 节点对当作时间锚 A、B,按”经过时长比例 × A→B 的总弧长 = 目标弧长”匀速通过。典型用途:纯粹用来塑形的几何锚(Bezier 控制中段、急转弯等),其 time 是为画路径形状而填的而非节拍,标为非 on-time 可避免本体在那个时间点上视觉”卡顿”。首节点与末节点强制为 on-time(loader 自动校正)。判定窗口、得分、持有、末段判定、真节点 SFX 时机都不受影响——这个字段仅影响 autoplay 节奏。序列化器在值为 true 时省略字段,未启用该特性的旧谱字节级保留。

Slide 玩法(finger-driven 模型)

  • 首节点按 tap 判定(同 tap note);
  • 首节点判定确定后(真 tap 或 ±120ms 后自动 break),slide 进入拖动状态——slide 本体跟随按住它的手指(手指在 slide 当前位置 240px 内即视为持有);
  • 中段分段判定:[首节点 + 120ms, 末节点] 按 240ms 切段(末尾余量 ≥ 180ms 自成一段;更短的余量并入前一段,不增加物量,让最后一个中段判定落在 slide 结束时刻)。每段是独立判定单元、drag 式二值:段内任一时刻”被持有 AND slide 位于轨迹 240px 内”→ Pure+,否则 → Break;
  • 终点判定(独立 1 单元):在末节点 ±120ms 内,本体到末节点的最近距离 ≤240px → Pure+,否则 Break;
  • 首节点 tap 也是独立单元——没有合成评级:每个单元在其结算时刻立即独立计分。短 slide(<300ms)无中段(仅首节点 + 终点)。

Drag

无需点击。判定时间内任何手指位于命中区域即可触发。

{ "type": "drag", "time": 6.0, "x": 0.5, "y": 0.4 }

Flick

带方向的快速滑动。

{ "type": "flick", "time": 7.0, "x": 0.5, "y": 0.5, "direction": 90 }

direction:角度(度数)——0 = 右,90 = 上,180 = 左,270 = 下。


判定

每个音符的触发时机依据以下四档评定:

评级 含义
Pure+ 最高精度命中
Pure 标准命中
Connect 偏早或偏晚,但仍在判定窗口内
Break 漏掉或超出判定窗口

具体的时序判定逻辑依音符类型而异:

  • Tap / Ex-Tap / Hold / Ex-Hold / Slide / Ex-Slide(起始点):按相对判定时间的绝对距离,在判定窗口内按差值分档。
  • Drag:只要命中在判定窗口内,一律为 Pure+。
  • Flick:窗口内方向匹配(±45°)的划动 → Pure+;其余情况(包括只点击不划动)→ Break。
  • Hold / Ex-Hold 与 Slide / Ex-Slide(持续段):按独立判定单元计分——首节点 tap 一个单元;中段 [起始 + 120ms, 结束] 每 240ms 一段(段内出现过有效按压 → Pure+,否则 Break,slide 另需本体在轨迹上),各一个单元;slide 终点再加一个二值单元。谱面总物量 = 全部单元之和,长 hold/slide 像一串 drag 一样贡献多个判定。短于 300ms 的不产生中段。

包结构示例

mynick_mysong.yzcpkg  (ZIP)
├── song.json
├── Stable.yzc.json
├── Drifting.yzc.json
├── Faint.yzc.json
├── Relink.yzc.json
├── music.ogg
└── cover.jpg