using UnityEngine; /// /// 判定区几何工具:把"音符 Collider 与判定线 Collider 在 Y 轴是否重叠"这一原本由 /// Physics2D 触发器(OnTriggerEnter2D/Exit2D)判断的条件,改为纯几何/时间计算, /// 用于去掉每帧移动 Collider 触发的 Physics2D 开销,同时保持判定"进入/离开判定区" /// 的时刻与原物理触发**完全一致**。 /// /// 原理:判定线静态不动(世界 Y 固定),音符沿 -Y 匀速下落。两个 AxisAligned Box 在 Y 轴 /// 重叠 ⇔ 音符 collider 上边缘 ≥ 判定线下边缘 且 音符 collider 下边缘 ≤ 判定线上边缘。 /// 用音符 transform.position.y 表达为一对阈值: /// 进入(overlap 开始): noteY ≤ lineTop + noteHalfH - noteOffY /// 离开(overlap 结束): noteY < lineBottom - noteHalfH - noteOffY /// 这与物理 trigger 的 enter/exit 触发时刻等价(同一 AABB 重叠判据)。 /// /// 判定结果本身仍由 hitTime(时间)决定,本类只复现"何时注册/注销到 TrackKeyManager"。 /// public static class JudgeZoneGeometry { private static bool _lineCaptured; private static float _lineTopY; // 判定线 collider 世界上边缘 private static float _lineBottomY; // 判定线 collider 世界下边缘 public static bool LineReady => _lineCaptured; /// /// 捕获判定线 collider 的世界 Y 范围(只需一次,判定线静态不动)。 /// 传入任一带 tag "JudgmentLine" 的 Collider2D。 /// public static void CaptureLine(Collider2D lineCollider) { if (_lineCaptured || lineCollider == null) return; Transform t = lineCollider.transform; if (lineCollider is BoxCollider2D box) { // 直接由 size/offset/世界缩放算,避免依赖 bounds 的物理同步时机。 float scaleY = Mathf.Abs(t.lossyScale.y); float centerY = t.position.y + box.offset.y * scaleY; float halfH = box.size.y * 0.5f * scaleY; _lineTopY = centerY + halfH; _lineBottomY = centerY - halfH; } else { Physics2D.SyncTransforms(); Bounds b = lineCollider.bounds; _lineTopY = b.max.y; _lineBottomY = b.min.y; } _lineCaptured = true; } /// 切场景/重开时清空,让新判定线重新捕获。 public static void Reset() { _lineCaptured = false; } /// /// 从音符自身 Collider 计算它的进入/离开阈值(以 transform.position.y 表示)。 /// halfH = collider 世界半高;offY = collider 中心相对 transform 原点的 Y 偏移(世界)。 /// public static bool TryGetThresholds(Collider2D noteCollider, Transform noteTf, out float enterY, out float exitY) { enterY = 0f; exitY = 0f; if (!_lineCaptured || noteCollider == null || noteTf == null) return false; float halfH, offY; // 关键:不能用 Collider2D.bounds——本工程 Physics2D AutoSyncTransforms=0, // 且音符 Rigidbody2D.simulated=false,bounds 不随 transform 主动重算而滞后。 // 对 hold 的 middle/end 段(被 baseSpawnYOffset 大幅上移)会算出错误的 offY, // 导致 enterY/exitY 整体错位、中尾段几乎进不了判定区。 // 改为直接用 BoxCollider2D 的 size/offset + 世界缩放计算(与物理同步时机无关)。 if (noteCollider is BoxCollider2D box) { float scaleY = Mathf.Abs(noteTf.lossyScale.y); halfH = box.size.y * 0.5f * scaleY; // collider 世界半高 offY = box.offset.y * scaleY; // collider 中心相对 transform 原点的世界 Y 偏移 } else { // 其它 collider 类型:先强制同步一次再读 bounds,尽量准确。 Physics2D.SyncTransforms(); Bounds b = noteCollider.bounds; halfH = b.extents.y; offY = b.center.y - noteTf.position.y; } // overlap 开始:音符下边缘(noteY+offY-halfH) ≤ 判定线上边缘。 enterY = _lineTopY + halfH - offY; // overlap 结束:音符上边缘(noteY+offY+halfH) < 判定线下边缘。 exitY = _lineBottomY - halfH - offY; return true; } }