using System; using System.Collections.Generic; using Dreamteck.Splines; using Ichni.RhythmGame.Beatmap; using Lean.Pool; using Unity.VisualScripting; using UnityEngine; namespace Ichni.RhythmGame { public class Hold : NoteBase { #region [特有属性字段] Special Fields public float holdEndTime; public float holdingTime; public bool isHolding; public float holdingBufferTime; public float bufferTimer; public float preTimeDifference; public NoteJudgeType preJudgeType; public float postTimeDifference; public NoteJudgeType postJudgeType; protected List startHoldEffects; protected List holdingEffects; #endregion #region [生成与初始化] Generation & Initialization public static Hold GenerateElement(string elementName, Guid id, List tags, bool isFirstGenerated, GameElement parentElement, float exactJudgeTime, float holdEndTime) { Hold hold = LeanPool.Spawn(GameManager.Instance.basePrefabs.holdNote, parentElement.transform).GetComponent(); hold.Initialize(elementName, id, tags, isFirstGenerated, parentElement); hold.exactJudgeTime = exactJudgeTime; hold.holdEndTime = holdEndTime; hold.holdingTime = 0; hold.holdingBufferTime = 0.04f; hold.judgeIntervals = NoteJudgeIntervals.HoldDefault; hold.preJudgeType = NoteJudgeType.NotJudged; hold.postJudgeType = NoteJudgeType.NotJudged; if (parentElement.TryGetComponent(out Track track) && track.trackTimeSubmodule != null) { hold.track = track; hold.trackPositioner.enabled = true; hold.trackPositioner.spline = track.trackPathSubmodule.path; hold.trackPositioner.updateMethod = SplineUser.UpdateMethod.LateUpdate; hold.isOnTrack = true; hold.UpdateNoteInTrack(CoreServices.TimeProvider.SongTime); } else { hold.track = null; hold.isOnTrack = false; } return hold; } public override void AfterInitialize() { base.AfterInitialize(); startHoldEffects = GetEffectListSafe("StartHold"); holdingEffects = GetEffectListSafe("Holding"); } public override void SetDefaultSubmodules() { base.SetDefaultSubmodules(); NoteAudioSubmodule = new NoteAudioSubmodule(this, "DefaultTap"); // 注意以前是你写死的 DefaultTap,依据需求核对 } #endregion #region [主循环阶段] Main Update public override bool ManualUpdate(float currentSongTime) { if (!base.ManualUpdate(currentSongTime)) return false; if (!isFirstJudged && !isDuringJudging && currentSongTime >= exactJudgeTime + judgeIntervals.beforeMiss.intervalStart && !GameManager.Instance.noteJudgeManager.checkingHoldList.Contains(this)) { isDuringJudging = true; GameManager.Instance.noteJudgeManager.checkingHoldList.Add(this); } if (!GameManager.Instance.songPlayer.isUpdating || isFinalJudged) return true; if (SettingsManager.instance.gameSettings.autoPlay && isFirstJudged) ExecuteProcessJudge(); if (isHolding) { holdingTime = currentSongTime - exactJudgeTime; bufferTimer = holdingBufferTime; } else if (isFirstJudged) { bufferTimer -= Time.deltaTime; } if (isOnTrack) UpdateNoteInTrack(currentSongTime); if (isDuringJudging) noteScreenPosition = GetScreenPosition(); SetJudgeArea(); if (!isFirstJudged && exactJudgeTime < currentSongTime) SlowOffsetAfterExactJudgeTime(); UpdateHoldEffects(); if (SettingsManager.instance.gameSettings.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime) { ExecuteStartJudge(currentSongTime); } CheckHoldLifeCycle(currentSongTime); return true; } private void CheckHoldLifeCycle(float currentSongTime) { if (isFirstJudged && currentSongTime > holdEndTime) { isHolding = false; isFinalJudged = true; ExecuteFinalJudge(currentSongTime); RemoveFromCheckingList(); } else if (isFirstJudged && bufferTimer < 0f) { isHolding = false; isFinalJudged = true; DisruptHoldEffects(); ExecuteFinalJudge(currentSongTime); RemoveFromCheckingList(); } else if (!isFirstJudged && currentSongTime > exactJudgeTime + judgeIntervals.afterMiss) { isFirstJudged = true; isFinalJudged = true; DisruptHoldEffects(); Miss(exactJudgeTime + judgeIntervals.afterMiss); RemoveFromCheckingList(); } } protected override void RemoveFromCheckingList() { if (GameManager.Instance.noteJudgeManager.checkingHoldList.Contains(this)) GameManager.Instance.noteJudgeManager.checkingHoldList.Remove(this); } #endregion #region [核心判定逻辑] Judgement Logic public bool CheckJudgeAvailability(InputUnitTap inputUnitTap) { if (isFirstJudged) return false; foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList) { if (!judgeUnit.CheckJudgeAvailability(inputUnitTap)) return false; } return true; } public bool CheckJudgeAvailability(InputUnitTouch inputUnitTouch) { if (!isFirstJudged) return false; foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList) { if (!judgeUnit.CheckJudgeAvailability(inputUnitTouch)) return false; } return true; } public override void ExecuteStartJudge(float triggerTime) { preTimeDifference = triggerTime - exactJudgeTime; preJudgeType = GetStartJudgeType(preTimeDifference); isFirstJudged = true; isHolding = true; NoteAudioSubmodule.PlayHoldStartAudio(); } public void ExecuteProcessJudge() { isHolding = true; } public void ExecuteFinalJudge(float triggerTime) { if (startHoldEffects != null) { for (int i = 0; i < startHoldEffects.Count; i++) { if (startHoldEffects[i].nowEffectState == EffectBase.EffectState.Middle) startHoldEffects[i].Adjust(); } } if (holdingEffects != null) { for (int i = 0; i < holdingEffects.Count; i++) holdingEffects[i].Adjust(); } postTimeDifference = holdEndTime - triggerTime; if (postTimeDifference <= 0.1f) postJudgeType = NoteJudgeType.Perfect; else if (postTimeDifference <= 0.125f) postJudgeType = NoteJudgeType.Good; else postJudgeType = NoteJudgeType.Bad; NoteJudgeType finalJudge = GetLowerType(preJudgeType, postJudgeType); float finalTimeDifference = Mathf.Min(preTimeDifference, postTimeDifference); GameManager.Instance.playingRecorder.resultData.Add(finalTimeDifference); switch (finalJudge) { case NoteJudgeType.Perfect: Perfect(triggerTime); break; case NoteJudgeType.Good: Good(triggerTime); break; case NoteJudgeType.Bad: Bad(triggerTime); break; case NoteJudgeType.Miss: Miss(triggerTime); break; } if (finalJudge != NoteJudgeType.Miss) NoteAudioSubmodule.PlayGeneralJudgeAudios(); } protected override void SetJudgeArea() { if (!SettingsManager.instance.gameSettings.debugMode || NoteJudgeSubmodule == null) return; if (isDuringJudging && !isFirstJudged) { foreach (NoteJudgeUnit unit in NoteJudgeSubmodule.judgeUnitList) { if (!unit.isShowingJudge) unit.SetShowingJudge(true); } } foreach (NoteJudgeUnit unit in NoteJudgeSubmodule.judgeUnitList) { if (unit.isShowingJudge) unit.UpdateJudge(); } if (!isDuringJudging && CoreServices.TimeProvider.SongTime > holdEndTime - 2 * Time.deltaTime) { foreach (NoteJudgeUnit unit in NoteJudgeSubmodule.judgeUnitList) { if (unit.isShowingJudge) unit.SetShowingJudge(false); } } } #endregion #region [特效与物理偏移] Special Hold Logic public override void UpdateNoteInMovableTrack(float songTime) { if (!isHolding && !isFinalJudged) base.UpdateNoteInMovableTrack(songTime); if (noteVisual is INoteVisualHold noteVisualHold) noteVisualHold.UpdateHoldInMovableTrack(); } public override void UpdateNoteInStaticTrack(float songTime) { base.UpdateNoteInStaticTrack(songTime); if (noteVisual is INoteVisualHold noteVisualHold) noteVisualHold.UpdateHoldInStaticTrack(); } public override void SetPerfectPosition() { if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable) { holdingTime = holdEndTime - exactJudgeTime; (noteVisual as INoteVisualHold)?.UpdateHoldInMovableTrack(); } } protected override void SlowOffsetAfterExactJudgeTime() { if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable) { holdingTime = CoreServices.TimeProvider.SongTime - exactJudgeTime; (noteVisual as INoteVisualHold)?.UpdateHoldInMovableTrack(); } } private void UpdateHoldEffects() { if (startHoldEffects != null) for (int i = 0; i < startHoldEffects.Count; i++) startHoldEffects[i].UpdateEffect(exactJudgeTime); if (holdingEffects != null) for (int i = 0; i < holdingEffects.Count; i++) holdingEffects[i].UpdateEffect(exactJudgeTime); } private void DisruptHoldEffects() { if (startHoldEffects != null) for (int i = 0; i < startHoldEffects.Count; i++) startHoldEffects[i].Disrupt(); } #endregion #region [谱面功能] Beatmap Method #endregion } }