519 lines
18 KiB
C#
519 lines
18 KiB
C#
using System.Collections;
|
||
using UnityEngine;
|
||
|
||
public enum NoteSegment { None, Start, Middle, End }
|
||
|
||
[RequireComponent(typeof(HoldNoteController))]
|
||
public class HoldNote : BaseNote
|
||
{
|
||
private KeyCode keyToPress = KeyCode.None;
|
||
private string noteColor = string.Empty;
|
||
private string noteID = string.Empty;
|
||
private NoteSegment segment = NoteSegment.None;
|
||
|
||
private float hitTime = 0f;
|
||
private float releaseTime = 0f;
|
||
private bool hasReleased = true;
|
||
private bool hasEnteredLine = false;
|
||
|
||
private Coroutine autoReturnCoroutine;
|
||
private HoldNoteController controller;
|
||
private AnimationController anim;
|
||
|
||
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 bool isJudged = false; // 分段判定标记,一旦判定(无论成功失败)就设为true,避免重复判定
|
||
private bool isHoldActive = false; // 长按有效标记:Start段成功判定且按键未松开
|
||
|
||
// 新增变量
|
||
private bool hasBeenHeldFromStart = false; // 进入判定区时是否已按住 (用于辅助判断,但不再直接阻断判定)
|
||
|
||
[Header("判定区间配置")]
|
||
public NoteJudgeConfig judgeConfig; // 判定区间配置,需在预制体或生成时赋值
|
||
|
||
private void Awake()
|
||
{
|
||
controller = GetComponent<HoldNoteController>();
|
||
anim = GetComponent<AnimationController>();
|
||
if (controller != null)
|
||
{
|
||
controller.OnJudgeZoneChanged += OnJudgeZoneChanged;
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (controller != null)
|
||
{
|
||
controller.OnJudgeZoneChanged -= OnJudgeZoneChanged;
|
||
}
|
||
}
|
||
|
||
private void OnJudgeZoneChanged(bool inZone)
|
||
{
|
||
hasEnteredLine = inZone;
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
ResetState();
|
||
}
|
||
|
||
private void PlayHitAnimation()
|
||
{
|
||
if (anim != null)
|
||
{
|
||
// 激活动画效果 GameObject
|
||
anim.gameObject.SetActive(true);
|
||
anim.PlayDestroyAnimation(noteColor);
|
||
|
||
// 延迟禁用动画效果 GameObject,确保动画播放完毕
|
||
StartCoroutine(DelayedDisableAnimation(anim.gameObject, 0.5f)); // 0.5秒后禁用
|
||
}
|
||
}
|
||
|
||
private IEnumerator DelayedDisableAnimation(GameObject animationObject, float delay)
|
||
{
|
||
yield return new WaitForSeconds(delay);
|
||
animationObject.SetActive(false);
|
||
}
|
||
|
||
public void Setup(int id, int trackIndex, float speed, float time, float delay,
|
||
bool isEnd, float scheduledEnd, string color, KeyCode key, string type, NoteJudgeConfig judgeConfig)
|
||
{
|
||
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.judgeConfig = judgeConfig;
|
||
|
||
this.noteID = id.ToString();
|
||
this.keyToPress = key;
|
||
this.noteColor = color;
|
||
// hitTime now uses real time base passed in as 'time' plus delay
|
||
this.hitTime = time + delay;
|
||
this.segment = (delay == 0f) ? NoteSegment.Start :
|
||
(isEnd ? NoteSegment.End : NoteSegment.Middle);
|
||
this.hasEnteredLine = false;
|
||
this.hasReleased = false;
|
||
|
||
// 判定区间配置检查
|
||
if (judgeConfig == null)
|
||
Debug.LogError($"HoldNoteJudgeConfig is null! 判定区间配置未传入!track={trackIndex}");
|
||
else
|
||
Debug.Log($"HoldNoteJudgeConfig: perfect={judgeConfig.perfectRange}, great={judgeConfig.greatRange}, good={judgeConfig.goodRange}, miss={judgeConfig.missRange}");
|
||
|
||
// Debug info to help investigate end note visibility
|
||
Debug.Log($"[HoldNote.Setup] id={noteID} segment={segment} type={type} hitTime={hitTime:F2} scheduledEnd={scheduledEndTime:F2} color={color} track={trackIndex}");
|
||
|
||
// 在 Setup 时注册初始状态
|
||
JudgeManager.Instance?.RegisterNoteReleased(noteID, false);
|
||
if (segment == NoteSegment.Start)
|
||
JudgeManager.Instance?.RegisterStartJudged(noteID, false); // 初始为未判定
|
||
|
||
if (segment == NoteSegment.End)
|
||
JudgeManager.Instance?.RegisterScheduledEndTime(noteID, scheduledEndTime);
|
||
|
||
controller?.SetSpeed(speed);
|
||
controller?.SetSegmentDelay(delay);
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// 如果当前片段已经判定过,直接返回
|
||
if (isJudged) return;
|
||
|
||
// 自动 Miss 判定:当音符经过判定线且未被判定时
|
||
if (hasEnteredLine && !isJudged)
|
||
{
|
||
if (segment == NoteSegment.Start && Time.time > hitTime + judgeConfig?.missRange)
|
||
{
|
||
Debug.Log($"[HoldNote] START段超时自动Miss: {noteColor}");
|
||
HandleStart();
|
||
isJudged = true;
|
||
}
|
||
else if (segment == NoteSegment.End && Time.time > scheduledEndTime + (judgeConfig?.missRange ?? 0.3f))
|
||
{
|
||
Debug.Log($"[HoldNote] END段超时自动Miss: {noteColor}");
|
||
HandleEnd(true);
|
||
isJudged = true;
|
||
}
|
||
}
|
||
|
||
// **通用长按状态更新:**
|
||
if (isHoldActive && Input.GetKeyUp(keyToPress))
|
||
{
|
||
isHoldActive = false;
|
||
Debug.Log($"[HoldNote] 按键 {keyToPress} 松开,长按状态失效。NoteID: {noteID}, Segment: {segment}, Type: {type}");
|
||
|
||
// 当玩家松开按键时,向 JudgeManager 注册该长音符已被释放,确保尾部能正确检查释放状态
|
||
if (!hasReleased)
|
||
{
|
||
hasReleased = true;
|
||
releaseTime = Time.time;
|
||
JudgeManager.Instance?.RegisterNoteReleased(noteID, true);
|
||
Debug.Log($"[HoldNote] RegisterNoteReleased on KeyUp. NoteID: {noteID}, releaseTime={releaseTime:F2}");
|
||
}
|
||
}
|
||
|
||
if (JudgeManager.Instance.IsStartJudged(noteID) && Input.GetKey(keyToPress))
|
||
{
|
||
isHoldActive = true;
|
||
}
|
||
|
||
// Start段判定
|
||
if (segment == NoteSegment.Start && hasEnteredLine)
|
||
{
|
||
if (Input.GetKeyDown(keyToPress))
|
||
{
|
||
HandleStart();
|
||
isJudged = true; // 无论成功失败都标记为已判定,避免重复尝试
|
||
}
|
||
}
|
||
// Middle段判定:中段只负责视觉/回收,不做 End 判定统计
|
||
else if (segment == NoteSegment.Middle
|
||
&& JudgeManager.Instance.IsStartJudged(noteID)
|
||
&& isHoldActive
|
||
&& !JudgeManager.Instance.HasNoteReleased(noteID))
|
||
{
|
||
if (Time.time >= hitTime)
|
||
{
|
||
if (hasEnteredLine)
|
||
{
|
||
Debug.Log($"[HoldNote] Middle段通过 (持续长按): {noteColor}");
|
||
PlayHitAnimation();
|
||
// 把通过事件上报给 JudgeManager(记录中段通过)
|
||
JudgeManager.Instance?.RegisterMiddlePassed(noteID);
|
||
ReturnToPool();
|
||
isJudged = true;
|
||
}
|
||
}
|
||
}
|
||
// End段判定:现在 End 段判定由独立的尾部音符负责(即本对象仍可判定,但更重要的是尾部note会决定最终判定)
|
||
else if (segment == NoteSegment.End
|
||
&& JudgeManager.Instance.IsStartJudged(noteID)
|
||
&& !JudgeManager.Instance.HasNoteReleased(noteID))
|
||
{
|
||
if (Input.GetKeyUp(keyToPress))
|
||
{
|
||
HandleEnd();
|
||
isJudged = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnTriggerEnter2D(Collider2D collision)
|
||
{
|
||
if (!collision.CompareTag("JudgmentLine")) return;
|
||
|
||
hasEnteredLine = true;
|
||
|
||
if (segment == NoteSegment.Middle)
|
||
{
|
||
hasBeenHeldFromStart = Input.GetKey(keyToPress);
|
||
Debug.Log($"[HoldNote] Middle段进入判定线: color={noteColor}, isStartJudged={JudgeManager.Instance.IsStartJudged(noteID)}, hasReleased={JudgeManager.Instance.HasNoteReleased(noteID)}, isKeyHeld={Input.GetKey(keyToPress)}, hasBeenHeldFromStart={hasBeenHeldFromStart}");
|
||
|
||
if (autoReturnCoroutine != null)
|
||
StopCoroutine(autoReturnCoroutine);
|
||
}
|
||
else if (segment == NoteSegment.End)
|
||
{
|
||
if (autoReturnCoroutine != null)
|
||
StopCoroutine(autoReturnCoroutine);
|
||
|
||
// 如果在尾部进入判定区时玩家已经释放按键,则此时应立即做 End 判定
|
||
if (JudgeManager.Instance.HasNoteReleased(noteID) && !isJudged)
|
||
{
|
||
Debug.Log($"[HoldNote] End段进入判定线但已经释放,立即执行 End 判定: {noteColor}");
|
||
HandleEnd();
|
||
isJudged = true;
|
||
// ReturnToPool handled inside HandleEnd
|
||
return;
|
||
}
|
||
|
||
autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck());
|
||
}
|
||
}
|
||
|
||
private void OnTriggerExit2D(Collider2D collision)
|
||
{
|
||
if (!collision.CompareTag("JudgmentLine")) return;
|
||
|
||
if (segment == NoteSegment.Start)
|
||
{
|
||
if (!JudgeManager.Instance.IsStartJudged(noteID))
|
||
{
|
||
Debug.Log($"[HoldNote] Start段离开判定线未判定,补偿 Miss 并回收: {noteColor}");
|
||
// 确保未判定的 Start 段在离开判定线时登记为 Miss
|
||
HandleStart();
|
||
isJudged = true;
|
||
ReturnToPool();
|
||
}
|
||
}
|
||
else if (segment == NoteSegment.Middle)
|
||
{
|
||
Debug.Log($"[HoldNote] Middle段离开判定线强制回收: {noteColor}");
|
||
ReturnToPool();
|
||
}
|
||
else if (segment == NoteSegment.End)
|
||
{
|
||
if (autoReturnCoroutine != null)
|
||
{
|
||
StopCoroutine(autoReturnCoroutine);
|
||
autoReturnCoroutine = null;
|
||
}
|
||
|
||
// 在尾部离开判定线时,按如下规则处理:
|
||
// - 如果头部未判定:补偿 Miss 并回收
|
||
// - 如果头部已判定且玩家已经释放:执行 End 判定并回收
|
||
// - 如果头部已判定且玩家仍在按住:不要强制 Miss,也不要立即回收,等待玩家松手或超时处理
|
||
if (!isJudged)
|
||
{
|
||
if (!JudgeManager.Instance.IsStartJudged(noteID))
|
||
{
|
||
Debug.Log($"[HoldNote] End段离开判定线且头部未判定,补偿 Miss: {noteColor}");
|
||
if (!JudgeManager.Instance.HasNoteReleased(noteID))
|
||
HandleEnd(true);
|
||
ReturnToPool();
|
||
}
|
||
else
|
||
{
|
||
if (JudgeManager.Instance.HasNoteReleased(noteID))
|
||
{
|
||
Debug.Log($"[HoldNote] End段离开判定线且已释放,执行 End 判定: {noteColor}");
|
||
HandleEnd();
|
||
isJudged = true;
|
||
ReturnToPool();
|
||
}
|
||
else
|
||
{
|
||
// 头部已判定且玩家仍在按住:保持显示,不作回收或判定
|
||
Debug.Log($"[HoldNote] End段离开判定线,头部已判定且仍在按住,保持显示等待松手或超时: {noteColor}");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ReturnToPool();
|
||
}
|
||
}
|
||
}
|
||
|
||
private IEnumerator DelayedAutoReturnCheck()
|
||
{
|
||
yield return null;
|
||
|
||
if (segment == NoteSegment.End)
|
||
{
|
||
float timeToWait = Mathf.Max(0f, scheduledEndTime - Time.time);
|
||
if (timeToWait > 0f)
|
||
{
|
||
yield return new WaitForSeconds(timeToWait);
|
||
}
|
||
|
||
if (!gameObject.activeSelf)
|
||
{
|
||
yield break;
|
||
}
|
||
ReturnToPool();
|
||
}
|
||
}
|
||
|
||
private void HandleStart()
|
||
{
|
||
float pressTime = Time.time;
|
||
float offset = Mathf.Abs(pressTime - hitTime);
|
||
string result;
|
||
if (offset < 0.1f) // 判定成功
|
||
{
|
||
result = "Perfect";
|
||
Debug.Log($"[HoldNote] START判定成功(偏差={offset:F2}秒): {noteColor}");
|
||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||
isHoldActive = true; // 成功判定Start,长按状态激活
|
||
PlayHitAnimation();
|
||
}
|
||
else
|
||
{
|
||
result = "Miss";
|
||
Debug.Log($"[HoldNote] START Miss(偏差={offset:F2}秒): {noteColor}");
|
||
JudgeManager.Instance.RegisterStartJudged(noteID, false); // 明确标记未判定
|
||
isHoldActive = false; // Start Miss,长按状态不激活
|
||
}
|
||
InputManager.Instance?.ShowJudgeResult(trackIndex, result);
|
||
StartCoroutine(DelayedReturn());
|
||
}
|
||
|
||
private IEnumerator DelayedReturn()
|
||
{
|
||
yield return new WaitForSeconds(0.05f);
|
||
if (gameObject.activeSelf)
|
||
{
|
||
ReturnToPool();
|
||
}
|
||
}
|
||
|
||
private void HandleEnd(bool forceMiss = false)
|
||
{
|
||
// 记录释放时间并在 JudgeManager 中登记已释放状态
|
||
if (!hasReleased)
|
||
{
|
||
releaseTime = Time.time;
|
||
hasReleased = true;
|
||
JudgeManager.Instance?.RegisterNoteReleased(noteID, true);
|
||
}
|
||
|
||
string result;
|
||
|
||
// 如果玩家在尾部进入判定区前就松手,则仅登记释放,不在此处进行最终判定和回收,除非是强制 Miss
|
||
if (!hasEnteredLine && !forceMiss)
|
||
{
|
||
Debug.Log($"[HoldNote] HandleEnd: 提前松手,记录释放但等待尾部进入判定区再判定. NoteID={noteID}, color={noteColor}");
|
||
return;
|
||
}
|
||
|
||
// 现在进行正常判定逻辑
|
||
releaseTime = Time.time;
|
||
hasReleased = true;
|
||
JudgeManager.Instance.RegisterNoteReleased(noteID, true);
|
||
|
||
if (forceMiss || !JudgeManager.Instance.IsStartJudged(noteID))
|
||
{
|
||
result = "Miss";
|
||
Debug.LogWarning($"[HoldNote] END判定失败({noteColor}):{(forceMiss ? "强制Miss" : "头部未判定")}");
|
||
}
|
||
else if (judgeConfig != null)
|
||
{
|
||
float diff = Mathf.Abs(releaseTime - scheduledEndTime);
|
||
if (diff <= judgeConfig.perfectRange)
|
||
{
|
||
result = "Perfect";
|
||
}
|
||
else if (diff <= judgeConfig.greatRange)
|
||
{
|
||
result = "Great";
|
||
}
|
||
else if (diff <= judgeConfig.goodRange)
|
||
{
|
||
result = "Good";
|
||
}
|
||
else if (diff <= judgeConfig.missRange)
|
||
{
|
||
result = "Miss";
|
||
}
|
||
else
|
||
{
|
||
result = "Miss";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
float diff = Mathf.Abs(releaseTime - scheduledEndTime);
|
||
if (diff <= 0.1f)
|
||
{
|
||
result = "Perfect";
|
||
}
|
||
else if (diff < 0.2f)
|
||
{
|
||
result = "Great";
|
||
}
|
||
else if (diff < 0.3f)
|
||
{
|
||
result = "Good";
|
||
}
|
||
else
|
||
{
|
||
result = "Bad";
|
||
}
|
||
}
|
||
InputManager.Instance?.ShowJudgeResult(trackIndex, result);
|
||
teamUIController.Instance?.OnJudgeResult(result); // 新增:更新combo计数
|
||
Debug.Log($"[HoldNote] END判定结果: {noteColor} {result} (release={releaseTime:F2}, target={scheduledEndTime:F2})");
|
||
ReturnToPool();
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
if (autoReturnCoroutine != null)
|
||
{
|
||
StopCoroutine(autoReturnCoroutine);
|
||
autoReturnCoroutine = null;
|
||
}
|
||
|
||
if (segment == NoteSegment.End &&
|
||
!isJudged && // 尚未判定
|
||
JudgeManager.Instance.IsStartJudged(noteID)) // 头部已判定
|
||
{
|
||
// 只在头部已判定且玩家已松手时补偿 End 判定为 Miss;否则不要强制 Miss
|
||
if (JudgeManager.Instance.HasNoteReleased(noteID) && !isHoldActive)
|
||
{
|
||
Debug.Log($"[HoldNote] OnDisable补偿End段判定(已释放): {noteColor}");
|
||
HandleEnd(); // 正常判定(基于 releaseTime)
|
||
isJudged = true;
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"[HoldNote] OnDisable:End段未判定且尚有按键状态,跳过强制 Miss: {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 if (segment == NoteSegment.End)
|
||
NotePool.Instance.ReturnHoldNoteEndSegment(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;
|
||
isJudged = false;
|
||
isHoldActive = false; // 重置长按标记
|
||
hasBeenHeldFromStart = false; // 重置
|
||
|
||
if (autoReturnCoroutine != null)
|
||
{
|
||
StopCoroutine(autoReturnCoroutine);
|
||
autoReturnCoroutine = null;
|
||
}
|
||
|
||
controller?.StopMovement();
|
||
}
|
||
} |