玉盘珍馐值万钱 缝缝补补又三天 长音已改逾卅遍
This commit is contained in:
@@ -22,6 +22,17 @@ public class NoteSpawner : MonoBehaviour
|
||||
[Tooltip("Global additional realtime offset (seconds) added to hit times for all notes. Use to test input latency or adjust judgement timing. Default 0. Can be negative to make notes arrive earlier.")]
|
||||
public float globalHitDelay = 0f;
|
||||
|
||||
// 新增:视觉下落速度倍率(public,可在 Inspector 调整)
|
||||
// 默认 1.0 = 原始速度。>1 加速下落(视觉更快),<1 减速下落(视觉更慢)。
|
||||
[Header("Visual speed settings")]
|
||||
[Tooltip("Multiplier applied to visual fall speed. Changing this will automatically adjust spawn timing so notes still arrive at their original beat times.")]
|
||||
[Range(0.8f, 1.25f)]
|
||||
public float speedMultiplier = 1f;
|
||||
|
||||
// optional: constants for runtime clamping (kept for internal use)
|
||||
private const float SpeedMultiplierMin = 0.8f;
|
||||
private const float SpeedMultiplierMax = 1.25f;
|
||||
|
||||
public NoteJudgeConfig judgeConfig; // 判定区间配置,需在 Inspector 赋值
|
||||
|
||||
private Beatmap beatmap;
|
||||
@@ -59,8 +70,15 @@ public class NoteSpawner : MonoBehaviour
|
||||
|
||||
foreach (NoteData note in beatmap.notes)
|
||||
{
|
||||
float travelTime = (60f / bpm) * 4;
|
||||
float spawnTime = note.time - travelTime;
|
||||
// clamp speed multiplier to supported range
|
||||
float sm = Mathf.Clamp(speedMultiplier, SpeedMultiplierMin, SpeedMultiplierMax);
|
||||
|
||||
// base travel time derived from BPM (beats -> seconds)
|
||||
float baseTravelTime = (60f / bpm) * 4f;
|
||||
// effective travel time adjusted by speedMultiplier. Clamp to avoid divide by zero.
|
||||
float effectiveTravelTime = baseTravelTime / Mathf.Max(0.0001f, sm);
|
||||
|
||||
float spawnTime = note.time - effectiveTravelTime;
|
||||
float delay = spawnTime - (Time.time - startTime) + spawnOffset;
|
||||
if (GameConfig.verboseLogs) Debug.Log($"delay: {delay}");
|
||||
|
||||
@@ -115,8 +133,15 @@ public class NoteSpawner : MonoBehaviour
|
||||
// pass realtime hit time to Note.Setup, include globalHitDelay
|
||||
float rawHit = startTime + noteData.time + globalHitDelay;
|
||||
float realtimeHit = Mathf.Max(0f, rawHit); // clamp to non-negative
|
||||
noteScript.Setup(key, noteData.trackIndex, CalculateSpeed(), realtimeHit, noteData.color, judgeConfig, noteData);
|
||||
if (GameConfig.verboseLogs) Debug.Log($"到达时间:{noteSpawnTime + (60f / bpm) * 4}");
|
||||
|
||||
// clamp multiplier locally and calculate travel time and speed consistent with speedMultiplier
|
||||
float smLocal = Mathf.Clamp(speedMultiplier, SpeedMultiplierMin, SpeedMultiplierMax);
|
||||
float baseTravelTime = (60f / bpm) * 4f;
|
||||
float effectiveTravelTime = baseTravelTime / Mathf.Max(0.0001f, smLocal);
|
||||
float speed = CalculateSpeed(effectiveTravelTime);
|
||||
|
||||
noteScript.Setup(key, noteData.trackIndex, speed, realtimeHit, noteData.color, judgeConfig, noteData);
|
||||
if (GameConfig.verboseLogs) Debug.Log($"到达时间:{noteSpawnTime + baseTravelTime}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -163,6 +188,12 @@ public class NoteSpawner : MonoBehaviour
|
||||
// 生成唯一 id(改为自增计数器,避免随机冲突)
|
||||
int holdNoteId = ++holdNoteIdCounter;
|
||||
|
||||
// calculate speed using effective travel time
|
||||
float smLocalHold = Mathf.Clamp(speedMultiplier, SpeedMultiplierMin, SpeedMultiplierMax);
|
||||
float baseTravelTimeHold = (60f / bpm) * 4f;
|
||||
float effectiveTravelTimeHold = baseTravelTimeHold / Mathf.Max(0.0001f, smLocalHold);
|
||||
float holdSpeed = CalculateSpeed(effectiveTravelTimeHold);
|
||||
|
||||
// 生成 Start 段
|
||||
GameObject startObj = notePool.GetStartNote(noteData.color);
|
||||
if (startObj == null)
|
||||
@@ -179,7 +210,13 @@ public class NoteSpawner : MonoBehaviour
|
||||
// pass realtime hit time (startTime + note.time) and delay 0, include globalHitDelay
|
||||
float rawStartHit = startTime + noteData.time + globalHitDelay;
|
||||
float startHit = Mathf.Max(0f, rawStartHit);
|
||||
holdNote.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), startHit, 0f, false, scheduledEndTime, noteData.color, key, "start", judgeConfig, noteData);
|
||||
holdNote.Setup(holdNoteId, noteData.trackIndex, holdSpeed, startHit, 0f, false, scheduledEndTime, noteData.color, key, "start", judgeConfig, noteData);
|
||||
|
||||
// compute visual scale for hold segments so pieces visually connect across speedMultiplier changes
|
||||
float visualScale = 0.9f * smLocalHold + 0.1f; // linear fit: f(1)=1, f(2)=1.9
|
||||
holdNote.ApplyVisualScale(visualScale);
|
||||
// inform hold note of visual speed so it can adapt judgement windows if needed
|
||||
holdNote.visualSpeedMultiplier = smLocalHold;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -190,7 +227,8 @@ public class NoteSpawner : MonoBehaviour
|
||||
// 生成 Middle 片段(1 .. segmentCount-1)
|
||||
for (int i = 1; i < segmentCount; i++)
|
||||
{
|
||||
float segmentDelay = i * actualSegmentInterval; // 使用实际间隔
|
||||
// keep segment delays based on actualSegmentInterval (unscaled) so middle pieces are consecutive regardless of visual speed
|
||||
float segmentDelay = i * actualSegmentInterval; // 不按 speedMultiplier 缩放,保证中段连续
|
||||
|
||||
GameObject segObj = notePool.GetHoldNoteSegment(noteData.color);
|
||||
if (segObj == null)
|
||||
@@ -208,7 +246,12 @@ public class NoteSpawner : MonoBehaviour
|
||||
// 中段都标记为 middle,pass realtime base time and segmentDelay
|
||||
float rawBase = startTime + noteData.time + globalHitDelay;
|
||||
float baseHit = Mathf.Max(0f, rawBase);
|
||||
holdSeg.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", judgeConfig, noteData);
|
||||
holdSeg.Setup(holdNoteId, noteData.trackIndex, holdSpeed, baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", judgeConfig, noteData);
|
||||
|
||||
// apply same visual scale so middle pieces visually connect
|
||||
float visualScaleMid = 0.9f * smLocalHold + 0.1f;
|
||||
holdSeg.ApplyVisualScale(visualScaleMid);
|
||||
holdSeg.visualSpeedMultiplier = smLocalHold;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -235,10 +278,15 @@ public class NoteSpawner : MonoBehaviour
|
||||
if (holdEnd != null)
|
||||
{
|
||||
// 将 delay 设置为整个 hold 长度 (或 segmentCount * actualSegmentInterval),确保尾部在最后中段之后
|
||||
float endDelay = segmentCount * actualSegmentInterval; // 通常等于 noteData.length
|
||||
float endDelay = segmentCount * actualSegmentInterval; // 不按 speedMultiplier 缩放,使用谱面长度保证尾段紧随中段
|
||||
float rawBase = startTime + noteData.time + globalHitDelay;
|
||||
float baseHit = Mathf.Max(0f, rawBase);
|
||||
holdEnd.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", judgeConfig, noteData);
|
||||
holdEnd.Setup(holdNoteId, noteData.trackIndex, holdSpeed, baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", judgeConfig, noteData);
|
||||
|
||||
// apply visual scale to end piece as well
|
||||
float visualScaleEnd = 0.9f * smLocalHold + 0.1f;
|
||||
holdEnd.ApplyVisualScale(visualScaleEnd);
|
||||
holdEnd.visualSpeedMultiplier = smLocalHold;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -247,9 +295,8 @@ public class NoteSpawner : MonoBehaviour
|
||||
}
|
||||
|
||||
|
||||
private float CalculateSpeed()
|
||||
private float CalculateSpeed(float noteTravelTime)
|
||||
{
|
||||
float noteTravelTime = (60f / bpm) * 4;
|
||||
return 10.75f / noteTravelTime;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user