Files
ichni_Official/Assets/Scripts/Game/GameElements/Notes/NoteObjects/NoteBase.cs
SoulliesOfficial 4031b29245 111
2025-08-27 21:45:18 -04:00

526 lines
19 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;
using System.Collections.Generic;
using System.Linq;
using Dreamteck.Splines;
using Sirenix.OdinInspector;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.Serialization;
namespace Ichni.RhythmGame
{
public abstract partial class NoteBase : GameElement, IHaveTimeDurationSubmodule, IComparable<NoteBase>
{
[Title("Basic Info")]
public float exactJudgeTime;
public NoteJudgeIntervals judgeIntervals;
[Title("Track Info")]
public bool isOnTrack;
public Track track;
public SplinePositioner trackPositioner;
[Title("Audio")]
public AudioContainer audioContainer;
[Title("NoteVisual")]
public NoteVisualBase noteVisual;
[Title("Submodules")]
public TimeDurationSubmodule timeDurationSubmodule { get; set; }
public NoteJudgeSubmodule noteJudgeSubmodule { get; set; }
public NoteAudioSubmodule noteAudioSubmodule { get; set; }
[Title("In-Game Info")]
public bool isDuringJudging;
public Vector2 noteScreenPosition;
public Vector2 perfectNoteScreenPosition;
public bool isFirstJudged;
public bool isFinalJudged;
public override int HierarchyPriority => -10;
[Title("Debug")] public TMP_Text judgeRankHint;
/// <summary>
/// 在MovableTrack上更新Note的位置注意HoldNote需要重写这个方法
/// </summary>
public virtual void UpdateNoteInMovableTrack()
{
TrackTimeSubmoduleMovable trackTimeSubmoduleMovable = track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
trackPositioner.SetPercent(trackTimeSubmoduleMovable.GetTrackPercent(exactJudgeTime));
}
/// <summary>
/// 在StaticTrack上更新Note的位置注意HoldNote需要重写这个方法
/// </summary>
public virtual void UpdateNoteInStaticTrack()
{
float songTime = GameManager.instance.songTime;
TrackTimeSubmoduleStatic trackTimeSubmoduleStatic = track.trackTimeSubmodule as TrackTimeSubmoduleStatic;
float startMove = exactJudgeTime - trackTimeSubmoduleStatic.trackTotalTime;
float percent = AnimationCurveEvaluator.Evaluate(trackTimeSubmoduleStatic.animationCurveType, (songTime - startMove) / trackTimeSubmoduleStatic.trackTotalTime);
percent = Mathf.Max(percent, 0);
percent = Mathf.Min(percent, 1);
trackPositioner.SetPercent(1 - percent);
}
public override void SetDefaultSubmodules()
{
timeDurationSubmodule = new TimeDurationSubmodule(this);
noteJudgeSubmodule = new NoteJudgeSubmodule(this);
}
public override void AfterInitialize()
{
perfectNoteScreenPosition = -Vector2.one;
float beyondTime = 0f;
foreach (EffectBase effectBase in noteVisual.effectSubmodule.effectCollection["Generate"])
{
if (effectBase is NoteGenerateEffect ge)
{
ge.Recover();
beyondTime = Mathf.Max(beyondTime, ge.generateTime);
}
else
{
effectBase.Recover();
}
}
if (exactJudgeTime - beyondTime - 0.5f > -GameManager.instance.songInformation.delay)
{
gameObject.SetActive(false);
GameManager.instance.noteManager.RegisterNote(this, exactJudgeTime - beyondTime - 0.5f);
}
//judgeRankHint = Instantiate(GameManager.instance.basePrefabs.judgeRankHint, noteVisual.transform).GetComponent<TMP_Text>();
}
protected virtual void Update()
{
if (!GameManager.instance.audioManager.isUpdating || isFinalJudged)
{
return;
}
if (isOnTrack)
{
if (track.trackTimeSubmodule is TrackTimeSubmoduleStatic)
{
UpdateNoteInStaticTrack();
}
}
if (perfectNoteScreenPosition == -Vector2.one) // -Vector2.one是一个不可能的屏幕位置用来标记perfectNoteScreenPosition还没有被赋值
{
if (isDuringJudging)
{
noteScreenPosition = GetScreenPosition();
}
if (exactJudgeTime <= GameManager.instance.songTime)
{
perfectNoteScreenPosition = noteScreenPosition;
}
}
/*if (!isFirstJudged && exactJudgeTime <= GameManager.instance.songTime)
{
SlowOffsetAfterExactJudgeTime();
}*/
SetJudgeArea();
//SetJudgeRankText();
foreach (EffectBase e in noteVisual.effectSubmodule.effectCollection["Generate"])
{
e.UpdateEffect(exactJudgeTime);
}
if (!isFirstJudged && GameManager.instance.songTime > exactJudgeTime + judgeIntervals.afterMiss)
{
Miss(exactJudgeTime + judgeIntervals.afterMiss);
GameManager.instance.playingRecorder.resultData.Add(judgeIntervals.afterMiss);
isFirstJudged = true;
isFinalJudged = true;
RemoveFromCheckingList();
}
}
protected virtual NoteJudgeType GetStartJudgeType(float timeDifference)
{
return judgeIntervals.GetNoteJudgeType(timeDifference);
}
protected virtual void RemoveFromCheckingList()
{
throw new NotImplementedException();
}
public virtual void ExecuteStartJudge()
{
}
public void UpdateNoteInTrack()
{
if (isOnTrack && track.trackTimeSubmodule != null)
{
if (track.trackTimeSubmodule is TrackTimeSubmoduleMovable)
{
UpdateNoteInMovableTrack();
}
else if (track.trackTimeSubmodule is TrackTimeSubmoduleStatic)
{
UpdateNoteInStaticTrack();
}
}
}
public virtual void Perfect(float triggerTime)
{
isDuringJudging = false;
GameManager.instance.playingRecorder.AddPerfect();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Perfect);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(triggerTime));
foreach (var e in noteVisual.effectSubmodule.effectCollection["Perfect"])
{
if (!e.IsPost)
{
e.UpdateEffect(triggerTime);
}
}
foreach (var e in noteVisual.effectSubmodule.effectCollection["Perfect"])
{
if (e.IsPost)
{
e.UpdateEffect(triggerTime);
}
}
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
if (isOnTrack) track.childElementList.Remove(this);
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.SetShowingJudge(false);
}
Destroy(gameObject, 1.2f); //注意所有特效时间不得超过1.2秒
}
public virtual void Good(float triggerTime)
{
isDuringJudging = false;
GameManager.instance.playingRecorder.AddGood();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Good);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["Good"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
if (isOnTrack) track.childElementList.Remove(this);
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.SetShowingJudge(false);
}
Destroy(gameObject, 1.2f);
}
public virtual void Bad(float triggerTime){
{
isDuringJudging = false;
GameManager.instance.playingRecorder.AddBad();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Bad);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["Bad"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
if (isOnTrack) track.childElementList.Remove(this);
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.SetShowingJudge(false);
}
Destroy(gameObject, 1.2f);
}}
public virtual void Miss(float triggerTime)
{
isDuringJudging = false;
GameManager.instance.playingRecorder.AddMiss();
noteAudioSubmodule.PlayNoteJudgeAudios(NoteJudgeType.Miss);
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["Miss"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
if (isOnTrack) track.childElementList.Remove(this);
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.SetShowingJudge(false);
}
Destroy(gameObject, 1.2f);
}
}
public abstract partial class NoteBase
{
public Vector2 GetScreenPosition()
{
return GameManager.instance.cameraManager.gameCamera.gameCamera.WorldToScreenPoint(noteVisual.noteVisualPosition);
}
protected virtual void SetJudgeArea()
{
if (!SettingsManager.instance.gameSettings.debugMode)
{
return;
}
if (noteJudgeSubmodule != null)
{
if (isDuringJudging && !isFirstJudged)
{
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => !unit.isShowingJudge))
{
unit.SetShowingJudge(true);
}
}
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.UpdateJudge();
}
if (!isDuringJudging && (isFinalJudged))
{
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
{
unit.SetShowingJudge(false);
}
}
}
}
public int CompareTo(NoteBase other)
{
return exactJudgeTime.CompareTo(other.exactJudgeTime);
}
private void SetJudgeRankText()
{
float triggerTime = GameManager.instance.songTime;
float timeDifference = triggerTime - exactJudgeTime;
NoteJudgeType startJudgeType = GetStartJudgeType(timeDifference);
if (startJudgeType == NoteJudgeType.Perfect)
{
judgeRankHint.text = "PERFECT";
judgeRankHint.color = Color.cyan;
}
else if (startJudgeType == NoteJudgeType.Good)
{
judgeRankHint.text = "GOOD";
judgeRankHint.color = Color.green;
}
else if (startJudgeType == NoteJudgeType.Bad)
{
judgeRankHint.text = "BAD";
judgeRankHint.color = Color.magenta;
}
else if (startJudgeType == NoteJudgeType.Miss)
{
judgeRankHint.text = "MISS";
judgeRankHint.color = Color.white;
}
}
public virtual void SetPerfectPosition()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
float notePercent = movable.GetTrackPercent(GameManager.instance.songTime);
trackPositioner.SetPercent(notePercent);
}
}
protected virtual void SlowOffsetAfterExactJudgeTime()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
float slowedTime = (GameManager.instance.songTime - exactJudgeTime) * 0.8f;
float notePercent = movable.GetTrackPercent(exactJudgeTime + slowedTime);
trackPositioner.SetPercent(notePercent);
}
}
/*public virtual void SlowOffsetAfterExactJudgeTime()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
float timeDifference = GameManager.instance.songTime - exactJudgeTime;
float percent = Mathf.Lerp(0f, 1f, timeDifference / (judgeIntervals.afterMiss + 0.2f));
float slowedTime = (GameManager.instance.songTime - exactJudgeTime) * percent;
//float percent = Mathf.Lerp(0, 0.5f, timeDifference / judgeIntervals.afterMiss);
//float slowedTime = (GameManager.instance.songTime - exactJudgeTime) * 0.5f;
float notePercent = movable.GetTrackPercent(exactJudgeTime + slowedTime);
trackPositioner.SetPercent(notePercent);
}
}*/
}
public abstract partial class NoteBase
{
public enum NoteJudgeType
{
Perfect = 0,
Good = 1,
Bad = 2,
Miss = 3,
NotJudged = -999
}
public static NoteJudgeType GetLowerType(NoteJudgeType typeA, NoteJudgeType typeB)
{
if (typeA == NoteJudgeType.NotJudged) return typeB;
if (typeB == NoteJudgeType.NotJudged) return typeA;
return typeA > typeB ? typeA : typeB;
}
public class NoteJudgeIntervals
{
public TimeInterval beforeMiss;
public TimeInterval beforeBad;
public TimeInterval beforeGood;
public TimeInterval perfect;
public TimeInterval afterGood;
public TimeInterval afterBad;
public float afterMiss;
public NoteJudgeIntervals(TimeInterval beforeMiss, TimeInterval beforeBad, TimeInterval beforeGood,
TimeInterval perfect, TimeInterval afterGood, TimeInterval afterBad, float afterMiss)
{
this.beforeMiss = beforeMiss;
this.beforeBad = beforeBad;
this.beforeGood = beforeGood;
this.perfect = perfect;
this.afterGood = afterGood;
this.afterBad = afterBad;
this.afterMiss = afterMiss;
}
public NoteJudgeType GetNoteJudgeType(float timeDifference)
{
if (beforeMiss.IsInInterval(timeDifference))
{
return NoteJudgeType.Miss;
}
else if (beforeBad.IsInInterval(timeDifference))
{
return NoteJudgeType.Bad;
}
else if (beforeGood.IsInInterval(timeDifference))
{
return NoteJudgeType.Good;
}
else if (perfect.IsInInterval(timeDifference))
{
return NoteJudgeType.Perfect;
}
else if (afterGood.IsInInterval(timeDifference))
{
return NoteJudgeType.Good;
}
else if (afterBad.IsInInterval(timeDifference))
{
return NoteJudgeType.Bad;
}
return NoteJudgeType.Miss; // Default to Miss if no other condition is met
}
}
public class TimeInterval
{
public float intervalStart;
public float intervalEnd;
public TimeInterval(float start, float end)
{
intervalStart = start;
intervalEnd = end;
}
public bool IsInInterval(float time)
{
if (Mathf.Approximately(intervalStart, intervalEnd)) return false;
return time >= intervalStart && time <= intervalEnd;
}
}
public static string GetNoteTypeName(NoteBase note)
{
return note switch
{
Tap => "Tap",
Stay => "Stay",
Hold => "Hold",
Flick => "Flick",
_ => throw new NotImplementedException("Note type not recognized")
};
}
}
namespace Beatmap
{
public abstract class NoteBase_BM : GameElement_BM
{
public float exactJudgeTime;
public NoteBase_BM()
{
}
public NoteBase_BM(string elementName, Guid elementGuid, List<string> tags, GameElement_BM attachedElement, float exactJudgeTime)
: base(elementName, elementGuid, tags, attachedElement)
{
this.exactJudgeTime = exactJudgeTime;
}
public override void ExecuteBM()
{
throw new NotImplementedException();
}
}
}
}