using System.Collections; using System.Collections.Generic; using UnityEngine; public enum NoteSegment { None, Start, Middle, End } [RequireComponent(typeof(HoldNoteController))] public class HoldNote : BaseNote { [Header("判定容差")] public float startHitWindow = 0.1f; // 起始段判定窗口 public float endHitWindow = 0.15f; // 结束段判定窗口 public float segmentGracePeriod = 0.05f; // 分段间宽限时间 private KeyCode keyToPress = KeyCode.None; private string noteColor = string.Empty; private string noteID = string.Empty; private NoteSegment segment = NoteSegment.None; private new float hitTime = 0f; private float releaseTime = 0f; private bool hasReleased = true; private bool hasEnteredLine = false; private Coroutine autoReturnCoroutine; private HoldNoteController controller; private static Dictionary _holdNoteValidity = new Dictionary(); // 新增变量:按键按住检测 private bool _isKeyHeld = false; private float _lastKeyPressTime = 0f; private const float KEY_CHECK_INTERVAL = 0.05f; private Coroutine _keyCheckCoroutine; public int id; public int trackIndex; public float speed; public float startTime; public float delay; public bool isEnd; public float scheduledEndTime; public string color; public KeyCode key; public string type; // "start", "middle", or "end" private void Awake() { controller = GetComponent(); } private void OnEnable() { ResetState(); } public void Setup(int id, int trackIndex, float speed, float time, float delay, bool isEnd, float scheduledEnd, string color, KeyCode key, string type) { this.id = id; this.trackIndex = trackIndex; this.speed = speed; this.startTime = time; this.delay = delay; this.isEnd = isEnd; this.scheduledEndTime = scheduledEnd; this.color = color; this.key = key; this.type = type; this.noteID = id.ToString(); this.keyToPress = key; this.noteColor = color; this.hitTime = time + delay; this.segment = (delay == 0f) ? NoteSegment.Start : (isEnd ? NoteSegment.End : NoteSegment.Middle); this.hasEnteredLine = false; this.hasReleased = false; JudgeManager.Instance?.RegisterNoteReleased(noteID, false); if (segment == NoteSegment.Start) JudgeManager.Instance?.RegisterStartJudged(noteID, false); if (segment == NoteSegment.End) JudgeManager.Instance?.RegisterScheduledEndTime(noteID, scheduledEndTime); // 如果是起始段,初始化状态为true if (type == "start") { _holdNoteValidity[id] = true; } // 立即检查有效性 if (type != "start" && (!_holdNoteValidity.ContainsKey(id) || !_holdNoteValidity[id])) { JudgeMiss(); } controller?.SetSpeed(speed); controller?.SetSegmentDelay(delay); } private void Update() { // 检查长音符是否有效 if (!IsHoldNoteValid()) return; if (segment == NoteSegment.Start && Input.GetKeyDown(keyToPress)) { if (hasEnteredLine) HandleStart(); } else if (segment == NoteSegment.End && Input.GetKeyUp(keyToPress)) { HandleEnd(); } // 更新按键状态 if (segment == NoteSegment.Middle && hasEnteredLine) { if (JudgeManager.Instance.IsStartJudged(noteID) && !JudgeManager.Instance.HasNoteReleased(noteID)) { if (!Input.GetKey(keyToPress)) { // 中途松开按键,判定失败 _holdNoteValidity[id] = false; JudgeMiss(); } } } } private bool IsHoldNoteValid() { // 起始段总是有效(因为它决定有效性) if (segment == NoteSegment.Start) return true; // 其他段检查有效性 return _holdNoteValidity.ContainsKey(id) && _holdNoteValidity[id]; } private IEnumerator CheckKeyHold() { while (true) { if (Input.GetKey(keyToPress)) { _isKeyHeld = true; _lastKeyPressTime = Time.time; } else if (_isKeyHeld && Time.time - _lastKeyPressTime > KEY_CHECK_INTERVAL) { // 按键松开超过检查间隔,判定为松开 _isKeyHeld = false; if (segment == NoteSegment.Middle || segment == NoteSegment.End) { // 中间段或结束段松开按键,标记长音符无效 _holdNoteValidity[id] = false; JudgeMiss(); yield break; // 停止检查 } } yield return new WaitForSeconds(KEY_CHECK_INTERVAL); } } private void OnTriggerEnter2D(Collider2D collision) { if (!collision.CompareTag("JudgmentLine")) return; if (!IsHoldNoteValid()) return; if (segment == NoteSegment.Start || segment == NoteSegment.Middle) { hasEnteredLine = true; } if (segment == NoteSegment.Middle) { //Debug.Log($"[HoldNote] Middle段进入判定线: color={noteColor}, isStartJudged={JudgeManager.Instance.IsStartJudged(noteID)}, hasReleased={JudgeManager.Instance.HasNoteReleased(noteID)}, isKeyDown={Input.GetKey(keyToPress)}"); if (autoReturnCoroutine != null) StopCoroutine(autoReturnCoroutine); autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck()); } if (segment == NoteSegment.End) { if (autoReturnCoroutine != null) StopCoroutine(autoReturnCoroutine); autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck()); } } private void OnTriggerExit2D(Collider2D collision) { if (!collision.CompareTag("JudgmentLine")) return; if (!IsHoldNoteValid()) return; if (segment == NoteSegment.Start) { if (!JudgeManager.Instance.IsStartJudged(noteID)) { ReturnToPool(); } } if (segment == NoteSegment.Middle) { if (!JudgeManager.Instance.IsStartJudged(noteID) || JudgeManager.Instance.HasNoteReleased(noteID)) { //Debug.Log($"[HoldNote] Middle段离开判定线强制回收: {noteColor}"); ReturnToPool(); } } else if (segment == NoteSegment.End) { if (!JudgeManager.Instance.IsStartJudged(noteID) || JudgeManager.Instance.HasNoteReleased(noteID)) { ReturnToPool(); } } } private IEnumerator DelayedAutoReturnCheck() { yield return null; if (!IsHoldNoteValid()) yield break; bool isStartJudgedBefore = JudgeManager.Instance.IsStartJudged(noteID); bool isKeyHeldNow = Input.GetKey(keyToPress); //Debug.Log($"[HoldNote] 延迟检查开始: noteID={noteID}, color={noteColor}, isStartJudged={isStartJudgedBefore}, isKeyHeld={isKeyHeldNow}"); if (!isStartJudgedBefore && isKeyHeldNow) { //Debug.Log($"[HoldNote] 延迟回补 Start 判定触发: {noteColor}"); HandleStart(); } bool isStartJudged = JudgeManager.Instance.IsStartJudged(noteID); bool hasReleased = JudgeManager.Instance.HasNoteReleased(noteID); bool isKeyHeld = Input.GetKey(keyToPress); //Debug.Log($"[HoldNote] 延迟确认状态: isStartJudged={isStartJudged}, hasReleased={hasReleased}, isKeyHeld={isKeyHeld}"); if (segment == NoteSegment.Middle && isStartJudged && isKeyHeld) { float waitTime = Mathf.Max(0f, hitTime - Time.time); //Debug.Log($"[HoldNote] Middle段通过延迟确认,准备等待到 hitTime 回收: {noteColor}, 剩余时间={waitTime:F2}s"); yield return new WaitForSeconds(waitTime); if (Input.GetKey(keyToPress) && !JudgeManager.Instance.HasNoteReleased(noteID)) { //Debug.Log($"[HoldNote] Middle段在 hitTime 达成条件,回收: {noteColor}"); ReturnToPool(); } else { //Debug.Log($"[HoldNote] Middle段在 hitTime 条件不符,不回收: {noteColor}"); } } else if (segment == NoteSegment.End && !hasReleased) { //Debug.Log($"[HoldNote] End段延迟检查后仍未释放: {noteColor}"); } else { //Debug.Log($"[HoldNote] DelayedAutoReturnCheck 无需处理: segment={segment}, color={noteColor}"); } } private void HandleStart() { if (!IsHoldNoteValid()) return; float pressTime = Time.time; float offset = Mathf.Abs(pressTime - hitTime); if (offset < startHitWindow) { //Debug.Log($"[HoldNote] START判定成功(偏差={offset:F2}秒): {noteColor}"); JudgeManager.Instance.RegisterStartJudged(noteID, true); JudgeManager.Instance.RegisterHoldNoteStart(noteID, Time.time); // 开始按键按住检测 _isKeyHeld = true; _lastKeyPressTime = Time.time; if (_keyCheckCoroutine != null) StopCoroutine(_keyCheckCoroutine); _keyCheckCoroutine = StartCoroutine(CheckKeyHold()); hasEnteredLine = true; StartCoroutine(DelayedReturn()); } else { _holdNoteValidity[id] = false; //Debug.Log($"[HoldNote] START Miss(偏差={offset:F2}秒,窗口={startHitWindow:F2}秒): {noteColor}"); JudgeMiss(); } } private void JudgeMiss() { // 执行Miss判定逻辑 //Debug.Log($"[HoldNote] 判定为Miss: {noteColor}"); ReturnToPool(); } private IEnumerator DelayedReturn() { yield return new WaitForSeconds(0.05f); ReturnToPool(); } private void HandleEnd() { if (!IsHoldNoteValid()) return; if (!JudgeManager.Instance.IsStartJudged(noteID)) { Debug.Log($"[HoldNote] 结束段判定失败:起始段未正确判定"); return; } releaseTime = Time.time; hasReleased = true; float diff = Mathf.Abs(releaseTime - scheduledEndTime); string result = "Miss"; if (diff < endHitWindow) { result = "Perfect"; JudgeManager.Instance.RegisterNoteReleased(noteID, true); ReturnToPool(); } else if (diff < endHitWindow * 2f) { result = "Good"; JudgeManager.Instance.RegisterNoteReleased(noteID, true); ReturnToPool(); } Debug.Log($"[HoldNote] END判定结果: {noteColor} {result} (release={releaseTime:F2}, target={scheduledEndTime:F2})"); } private void OnTriggerStay2D(Collider2D collision) { if (!collision.CompareTag("JudgmentLine")) return; if (!IsHoldNoteValid()) return; if (segment == NoteSegment.Middle && JudgeManager.Instance.IsStartJudged(noteID) && Input.GetKey(keyToPress)) { hasEnteredLine = true; } } private void OnDisable() { if (autoReturnCoroutine != null) { StopCoroutine(autoReturnCoroutine); autoReturnCoroutine = null; } if (_keyCheckCoroutine != null) { StopCoroutine(_keyCheckCoroutine); _keyCheckCoroutine = null; } if (segment == NoteSegment.End && !hasReleased && !JudgeManager.Instance.HasNoteReleased(noteID) && keyToPress != KeyCode.None && !Input.GetKey(keyToPress)) { hasReleased = true; JudgeManager.Instance.RegisterNoteReleased(noteID, true); //Debug.Log($"[HoldNote] OnDisable补偿KeyUp: {noteColor}"); } controller?.StopMovement(); } private void ReturnToPool() { if (!gameObject.activeSelf) return; if (segment == NoteSegment.Start && !hasEnteredLine) { //Debug.LogWarning($"[HoldNote] 阻止回收尚未进入判定线的 Start段: {noteColor}"); return; } gameObject.SetActive(false); if (segment == NoteSegment.Start) NotePool.Instance.ReturnStartNote(gameObject, noteColor); else NotePool.Instance.ReturnHoldNoteSegment(gameObject, noteColor); } public void ResetState() { hasReleased = false; segment = NoteSegment.None; keyToPress = KeyCode.None; noteColor = string.Empty; noteID = string.Empty; hitTime = 0f; scheduledEndTime = 0f; hasEnteredLine = false; _isKeyHeld = false; _lastKeyPressTime = 0f; if (autoReturnCoroutine != null) { StopCoroutine(autoReturnCoroutine); autoReturnCoroutine = null; } if (_keyCheckCoroutine != null) { StopCoroutine(_keyCheckCoroutine); _keyCheckCoroutine = null; } controller?.StopMovement(); } private void OnDestroy() { if (segment == NoteSegment.End && _holdNoteValidity.ContainsKey(id)) { _holdNoteValidity.Remove(id); } } }