Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/HoldNote.cs
T

519 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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] OnDisableEnd段未判定且尚有按键状态,跳过强制 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();
}
}