using System.Collections; using UnityEngine; public class NoteController : MonoBehaviour { private float speed; private bool isMoving = true; private bool isInJudgeZone = false; // Documentation text normalized. private ParticleSystem hitEffect; private Note linkedNote; private bool useAbsolutePositioning = false; // 去物理判定:用几何阈值复现原 Trigger 的进入/离开时刻。 private Collider2D _col; private float _enterY, _exitY; private bool _thresholdsReady; // For absolute position tracking instead of relative translate private Vector3 spawnPosition; private float activationTime = -1f; // When the note should start moving (hitTime - travelTime) private float baseSpawnYOffset = 0f; // Initial Y offset from compensation at spawn time private void Awake() { hitEffect = GetComponentInChildren(); linkedNote = GetComponent(); spawnPosition = transform.position; _col = GetComponent(); // 去物理判定:判定改为几何驱动,音符不再需要 Physics2D 模拟。 // 关闭 Rigidbody2D 模拟,消除每帧移动 kinematic 刚体触发的物理同步/重叠检测开销。 // 保留 Collider 组件本身(仅用于读取其 bounds 计算阈值,不参与物理)。 var rb = GetComponent(); if (rb != null) rb.simulated = false; } private void OnEnable() { NoteRuntimeTickManager.Register(this); } private void OnDisable() { NoteRuntimeTickManager.Unregister(this); } public void ManualTick(float songTime) { if (isMoving && useAbsolutePositioning) { // Use absolute positioning based on elapsed time since activation float elapsedSinceActivation = songTime - activationTime; float travelDistance = speed * Mathf.Max(0f, elapsedSinceActivation); // Position = spawnPoint + initial offset + downward travel Vector3 newPos = spawnPosition; newPos.y -= (baseSpawnYOffset + travelDistance); transform.position = newPos; } // 去物理判定:用音符 Y 与阈值的比较复现 OnTriggerEnter2D/Exit2D 的进入/离开时刻。 UpdateJudgeZoneByGeometry(); } private void UpdateJudgeZoneByGeometry() { if (linkedNote == null) return; if (!_thresholdsReady) { // 判定线捕获后,用音符自身 collider 计算进入/离开阈值(只算一次)。 if (!JudgeZoneGeometry.LineReady) return; if (!JudgeZoneGeometry.TryGetThresholds(_col, transform, out _enterY, out _exitY)) return; _thresholdsReady = true; } float noteY = transform.position.y; // 进入:noteY 下降到 ≤ enterY 且尚未进入。 if (!isInJudgeZone && noteY <= _enterY) { EnterJudgeZone(); // 单帧穿透(帧尖峰导致一帧内越过整个判定区):立即补一次离开,行为与物理一致。 if (noteY <= _exitY) ExitJudgeZone(); } // 离开:noteY 继续下降到 ≤ exitY 且当前在区内。 else if (isInJudgeZone && noteY <= _exitY) { ExitJudgeZone(); } } public void SetSpeed(float newSpeed) { speed = newSpeed; } public void StopMovement() { isMoving = false; } public void PlayHitEffect() { if (hitEffect != null) { hitEffect.Play(); } } /// /// Configure timing for absolute position tracking. /// Call this from NoteSpawner after the note is spawned. /// public void ConfigureAbsolutePositioning(Vector3 initialSpawnPos, float hitTime, float travelTime, float initialYOffset) { spawnPosition = initialSpawnPos; activationTime = hitTime - travelTime; baseSpawnYOffset = initialYOffset; useAbsolutePositioning = true; transform.position = GetExpectedPosition(GameplayClock.NowSongTime); if (JudgeManager.IsDebugEnabled) Debug.Log($"[NoteController] Configured: spawnPos={spawnPosition}, activationTime={activationTime:F3}, initialYOffset={initialYOffset:F4}"); } public Vector3 GetExpectedPosition(float time) { float elapsedSinceActivation = time - activationTime; float travelDistance = speed * Mathf.Max(0f, elapsedSinceActivation); Vector3 newPos = spawnPosition; newPos.y -= (baseSpawnYOffset + travelDistance); return newPos; } public void ResetState() { speed = 0f; isMoving = true; isInJudgeZone = false; activationTime = -1f; baseSpawnYOffset = 0f; spawnPosition = transform.position; useAbsolutePositioning = false; _thresholdsReady = false; // 池化复用:下次重新计算阈值。 if (hitEffect != null) { hitEffect.Stop(); hitEffect.Clear(); } } // 进入判定区(原 OnTriggerEnter2D 逻辑,保持完全一致)。 private void EnterJudgeZone() { if (linkedNote == null) return; isInJudgeZone = true; // register note by instance id so TrackKeyManager can prioritize // tap notes are short notes without hold TrackKeyManager.Instance.RegisterKey(linkedNote.GetTrackIndex(), linkedNote.GetCachedInstanceId(), "tap", linkedNote.HitTime); } // 离开判定区(原 OnTriggerExit2D 逻辑,保持完全一致)。 private void ExitJudgeZone() { if (linkedNote == null) return; isInJudgeZone = false; TrackKeyManager.Instance.UnregisterKey(linkedNote.GetTrackIndex(), linkedNote.GetCachedInstanceId()); linkedNote.SetJudgeZone(isInJudgeZone); } /// /// Query Note whether it's in judge zone /// public bool IsInJudgeZone() { return isInJudgeZone; } }