展前冒死重构
This commit is contained in:
@@ -35,8 +35,8 @@ public class NoteSpawner : MonoBehaviour
|
||||
|
||||
// Documentation text normalized.
|
||||
[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.5f, 2f)]
|
||||
public float speedMultiplier = 1f;
|
||||
[Range(1f, 3f)]
|
||||
public float speedMultiplier = 2f;
|
||||
|
||||
[Header("Calibration")]
|
||||
[Tooltip("Tolerance (world units) for snapping middle segments to expected position after spawn.")]
|
||||
@@ -84,8 +84,9 @@ public class NoteSpawner : MonoBehaviour
|
||||
public GameObject animations;
|
||||
|
||||
// optional: constants for runtime clamping (kept for internal use)
|
||||
private const float SpeedMultiplierMin = 0.5f;
|
||||
private const float SpeedMultiplierMax = 2f;
|
||||
// Player-facing speed is clamped to 1-3; FlowSpeedGlobalMultiplier makes the real range 2-6.
|
||||
private const float SpeedMultiplierMin = 1f;
|
||||
private const float SpeedMultiplierMax = 3f;
|
||||
// 全局流速系数:与用户可调流速(speedMultiplier)相乘的独立系数,加快音符下落。
|
||||
// 为什么用它而非 bpm 倍率:noteSpeed = 10.75 * sm * bpm / 240,若靠 ×bpm 提速,
|
||||
// 长条音符的视觉长度由 ApplyVisualScale(sm) 决定(只随 sm 不随 bpm)、且分段数 segmentInterval=(60/bpm)/4
|
||||
@@ -100,6 +101,28 @@ public class NoteSpawner : MonoBehaviour
|
||||
get { return Mathf.Clamp(speedMultiplier, SpeedMultiplierMin, SpeedMultiplierMax) * FlowSpeedGlobalMultiplier; }
|
||||
}
|
||||
|
||||
[Header("Note Fall Speed Source")]
|
||||
[Tooltip("true=沿用谱面 BPM 决定下落速度(旧逻辑,高 BPM 歌音符更快);false=用固定参考 BPM," +
|
||||
"下落速度不再随歌曲 BPM 变化,仅由流速 speedMultiplier 控制(全曲统一手感)。默认 false。")]
|
||||
public bool useBpmForSpeed = false;
|
||||
|
||||
[Tooltip("useBpmForSpeed=false 时用作速度基准的固定参考 BPM。等价于“所有歌曲都按此 BPM 的手感下落”。" +
|
||||
"建议 ≥60:地面粒子速度对 BPM 有 60 下限,低于 60 会使音符与粒子略微不同步。")]
|
||||
public float referenceBpm = 120f;
|
||||
|
||||
// 速度/长条分段公式统一使用的“有效 BPM”。开=真实谱面 bpm;关=固定参考 bpm。
|
||||
// 二者都同时喂给 noteSpeed 与 segmentInterval,故长条段间距(∝sm,bpm 相消)与地面粒子同步天然保持不变。
|
||||
public float EffectiveBpm
|
||||
{
|
||||
get { return useBpmForSpeed ? bpm : Mathf.Max(1f, referenceBpm); }
|
||||
}
|
||||
|
||||
[Header("Hold Note Body Fill")]
|
||||
[Tooltip("true=新逻辑(默认):长条中段不拉伸(保持 prefab 原始大小),改为按流速生成更多中段密铺填满(流速越快段越多)。" +
|
||||
"false=旧逻辑:中段数量固定,用 ApplyVisualScale(流速) 拉伸每段填满(流速越大越拉长)。" +
|
||||
"两种方案下长条视觉长度一致、判定完全不变(中段纯视觉、end 段到达时间不变)。")]
|
||||
public bool useMultiSegmentFill = true;
|
||||
|
||||
public NoteJudgeConfig judgeConfig; // Documentation text normalized.
|
||||
private Beatmap beatmap;
|
||||
private float startTime; // Absolute chart time anchor.
|
||||
@@ -113,7 +136,9 @@ public class NoteSpawner : MonoBehaviour
|
||||
private Dictionary<int, int> noteIndexToHoldId = new Dictionary<int, int>();
|
||||
|
||||
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
|
||||
private const float NoteSpeedDefault = 1f;
|
||||
private const string NoteSpeedDefaultVersionKey = "noteSpeedMultiplierDefaultVersion";
|
||||
private const int NoteSpeedDefaultVersion = 2;
|
||||
private const float NoteSpeedDefault = 2f;
|
||||
private const float BaseTravelDistance = 10.75f;
|
||||
|
||||
private Coroutine spawnCoroutine;
|
||||
@@ -146,10 +171,22 @@ public class NoteSpawner : MonoBehaviour
|
||||
{
|
||||
if (PlayerPrefs.HasKey(NoteSpeedPrefKey))
|
||||
{
|
||||
int version = PlayerPrefs.GetInt(NoteSpeedDefaultVersionKey, 0);
|
||||
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
|
||||
if (version < NoteSpeedDefaultVersion && Mathf.Approximately(saved, 3f))
|
||||
{
|
||||
saved = NoteSpeedDefault;
|
||||
}
|
||||
|
||||
saved = Mathf.Clamp(saved, SpeedMultiplierMin, SpeedMultiplierMax);
|
||||
PlayerPrefs.SetFloat(NoteSpeedPrefKey, saved);
|
||||
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
|
||||
PlayerPrefs.Save();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerPrefs.SetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
|
||||
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
@@ -280,10 +317,13 @@ public class NoteSpawner : MonoBehaviour
|
||||
|
||||
// Cache parameters that don't change within the loop
|
||||
float sm = EffectiveSpeedMultiplier;
|
||||
float baseTravelTime = (60f / bpm) * 4f;
|
||||
// 下落速度基准:useBpmForSpeed=true 用谱面 bpm(旧逻辑),false 用固定参考 bpm(速度不随歌曲变)。
|
||||
// 判定时间不受影响:到达时刻只由 note.time 决定,speed 仅改视觉下落快慢与出现时机。
|
||||
float speedBpm = EffectiveBpm;
|
||||
float baseTravelTime = (60f / speedBpm) * 4f;
|
||||
float baseNoteTravelTime = baseTravelTime / Mathf.Max(0.0001f, sm);
|
||||
float noteSpeed = CalculateSpeed(baseNoteTravelTime);
|
||||
float segmentInterval = (60f / bpm) / 4f;
|
||||
float segmentInterval = (60f / speedBpm) / 4f;
|
||||
|
||||
EnsureJudgmentLine();
|
||||
float[] laneTravelTimes = BuildLaneTravelTimes(noteSpeed);
|
||||
@@ -453,9 +493,24 @@ public class NoteSpawner : MonoBehaviour
|
||||
return -1;
|
||||
}
|
||||
|
||||
int segmentCount = Mathf.CeilToInt(noteData.length / segmentInterval);
|
||||
// 段间隔决定"每隔多少时间放一段"。段间距(世界坐标)= noteSpeed × 段间隔,而 noteSpeed ∝ 流速 sm。
|
||||
// 旧逻辑(useMultiSegmentFill=false):段间隔不随 sm 变 → 段间距 ∝ sm,段数固定,
|
||||
// 靠 ApplyVisualScale(sm) 把每段拉伸 sm 倍去填满变大的段间距(流速越大越拉长)。
|
||||
// 新逻辑(useMultiSegmentFill=true):段间隔 ÷ sm → 段数 ∝ sm,而段间距 = noteSpeed × (段间隔/sm) = 恒定,
|
||||
// 恰好等于不拉伸(scale=1)时 prefab 的原始段高,于是用等大的原始段密铺填满(流速越大段越多)。
|
||||
// 两种方案下:end 段到达时间 = baseHit + length 完全不变(段数变但 actualSegmentInterval=length/count 抵消),
|
||||
// 长条整体视觉长度一致;中段纯视觉、不参与计分/连击,故判定业务逻辑完全不受影响。
|
||||
float effectiveSegmentInterval = segmentInterval;
|
||||
if (useMultiSegmentFill)
|
||||
{
|
||||
effectiveSegmentInterval = segmentInterval / Mathf.Max(0.01f, sm);
|
||||
}
|
||||
int segmentCount = Mathf.CeilToInt(noteData.length / effectiveSegmentInterval);
|
||||
if (segmentCount < 2) segmentCount = 2; // Force at least one middle segment between start and end.
|
||||
float actualSegmentInterval = segmentCount > 0 ? noteData.length / segmentCount : segmentInterval;
|
||||
float actualSegmentInterval = segmentCount > 0 ? noteData.length / segmentCount : effectiveSegmentInterval;
|
||||
|
||||
// 新逻辑下不拉伸(每段保持 prefab 原始大小);旧逻辑下按流速 sm 拉伸每段。
|
||||
float holdVisualScale = useMultiSegmentFill ? 1f : sm;
|
||||
|
||||
Transform spawnPoint = spawnPoints[noteData.trackIndex];
|
||||
// Documentation text normalized.
|
||||
@@ -501,8 +556,8 @@ public class NoteSpawner : MonoBehaviour
|
||||
// pass realtime hit time (startTime + note.time) and delay 0, include globalHitDelay
|
||||
holdNote.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, 0f, false, scheduledEndTime, noteData.color, key, "start", travelTime, judgeConfig, noteData, isSync);
|
||||
|
||||
// 使用流速倍率 sm 直接进行缩放,确保长条音符在 0.5-2.0 范围内依然能完美衔接
|
||||
float visualScale = sm;
|
||||
// 旧逻辑=按流速 sm 拉伸;新逻辑(多段填充)=不拉伸(holdVisualScale=1)。
|
||||
float visualScale = holdVisualScale;
|
||||
holdNote.ApplyVisualScale(visualScale);
|
||||
// inform hold note of visual speed so it can adapt judgement windows if needed
|
||||
holdNote.visualSpeedMultiplier = sm;
|
||||
@@ -533,8 +588,8 @@ public class NoteSpawner : MonoBehaviour
|
||||
// Documentation text normalized.
|
||||
holdSeg.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", travelTime, judgeConfig, noteData, false);
|
||||
|
||||
// 应用与开始段一致的流速缩放
|
||||
float visualScaleMid = sm;
|
||||
// 与开始段一致:旧逻辑拉伸、新逻辑不拉伸(靠更多段密铺填满)。
|
||||
float visualScaleMid = holdVisualScale;
|
||||
holdSeg.ApplyVisualScale(visualScaleMid);
|
||||
holdSeg.visualSpeedMultiplier = sm;
|
||||
|
||||
@@ -565,8 +620,8 @@ public class NoteSpawner : MonoBehaviour
|
||||
{
|
||||
holdEnd.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", travelTime, judgeConfig, noteData, false);
|
||||
|
||||
// 同样应用流速缩放
|
||||
float visualScaleEnd = sm;
|
||||
// 与开始段一致:旧逻辑拉伸、新逻辑不拉伸。
|
||||
float visualScaleEnd = holdVisualScale;
|
||||
holdEnd.ApplyVisualScale(visualScaleEnd);
|
||||
holdEnd.visualSpeedMultiplier = sm;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user