445 lines
14 KiB
C#
445 lines
14 KiB
C#
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<int, bool> _holdNoteValidity = new Dictionary<int, bool>();
|
|
|
|
// 新增变量:按键按住检测
|
|
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<HoldNoteController>();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|