Files
ichni_Official/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Hold.cs
SoulliesOfficial 96619b1bb5 谱面替换
2026-07-30 09:28:43 -04:00

330 lines
12 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;
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<EffectBase> startHoldEffects;
protected List<EffectBase> holdingEffects;
#endregion
#region [] Generation & Initialization
public static Hold GenerateElement(string elementName, Guid id, List<string> tags, bool isFirstGenerated,
GameElement parentElement, float exactJudgeTime, float holdEndTime)
{
Hold hold = LeanPool.Spawn(GameManager.Instance.basePrefabs.holdNote, parentElement.transform).GetComponent<Hold>();
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 void ManualPrepareForJudge(float currentSongTime)
{
if (!isFirstJudged && !isDuringJudging &&
currentSongTime >= exactJudgeTime + judgeIntervals.beforeMiss.intervalStart &&
!GameManager.Instance.noteJudgeManager.checkingHoldList.Contains(this))
{
isDuringJudging = true;
GameManager.Instance.noteJudgeManager.checkingHoldList.Add(this);
}
// isHolding 会在上一帧末尾重置。已经开始的 Hold 必须保留上一帧收缩后的
// Visual供本帧 Touch 判定使用;否则 UpdateNoteInTrack 会先把头部展开回起点。
if (isFirstJudged && !isFinalJudged)
{
UpdateJudgeSpatialState(currentSongTime);
SetJudgeArea();
return;
}
base.ManualPrepareForJudge(currentSongTime);
}
protected override void UpdateSpatialStateBeforeJudgeScreenSample(float currentSongTime)
{
if (!isFirstJudged && exactJudgeTime < currentSongTime)
{
SlowOffsetAfterExactJudgeTime();
}
}
protected override void UpdateJudgeSpatialState(float currentSongTime)
{
if (isDuringJudging)
{
UpdateJudgeScreenPosition(currentSongTime);
}
if (perfectNoteScreenPosition == -Vector2.one && exactJudgeTime <= currentSongTime)
{
perfectNoteScreenPosition = noteScreenPosition;
}
}
public override bool ManualResolveAfterJudge(float currentSongTime)
{
if (!base.ManualResolveAfterJudge(currentSongTime)) return false;
if (!GameManager.Instance.songPlayer.isUpdating || isFinalJudged) return true;
if (SettingsManager.instance.settingsSaveData.autoPlay && isFirstJudged) ExecuteProcessJudge();
if (isHolding)
{
holdingTime = currentSongTime - exactJudgeTime;
bufferTimer = holdingBufferTime;
}
else if (isFirstJudged)
{
bufferTimer -= Time.deltaTime;
}
// Touch 判定已经在本帧设置 isHolding。现在使用最新 holdingTime 收缩 Hold
// 再采样实际头部位置,保证下一帧判定区域与 Visual 保持一致。
if (isOnTrack) UpdateNoteInTrack(currentSongTime);
if (isDuringJudging) UpdateJudgeScreenPosition(currentSongTime);
SetJudgeArea();
UpdateHoldEffects();
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(InputJudgeContext context)
{
if (context.InputUnit is InputUnitTap && isFirstJudged) return false;
if (context.InputUnit is InputUnitTouch && !isFirstJudged) return false;
if (context.InputUnit is not InputUnitTap && context.InputUnit is not InputUnitTouch) return false;
// 只限制 Hold 头部的 Tap起判成功后的 Touch 需要在整段 Hold 生命周期持续生效。
if (context.InputUnit is InputUnitTap && !IsWithinStartJudgeWindow(context.InputSongTime)) return false;
foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList)
{
if (!judgeUnit.CheckJudgeAvailability(context)) 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.PlayHoldEndAudio();
}
protected override void SetJudgeArea()
{
if (!SettingsManager.instance.settingsSaveData.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(songTime);
}
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
}
}