using System; using System.Collections; using Convention; using Demo.Editor.UI; using UnityEngine; using UnityEngine.Events; namespace Demo.Game { public interface IHookInteraction { } public abstract class IInteraction : TimelineScriptObject { protected override void SetupTimelineItem(TimelineItem item) { item.SetupDuration(new(VisibleDuration.x, VisibleDuration.y), GetTimelineItemColor()); } [Serializable] public enum DurationStats { BeforeBegin = 0, BeforeEnd, After, Judgement } public const float IdentifiableJudgementTerminal = 0.16f; [Header(nameof(VisibleDuration))] [Content, SerializeField] private Vector2 VisibleDuration; public static float DefaultVisibleLength = IdentifiableJudgementTerminal * 15; [Content, SerializeField] public UnityEvent VisibleDurationBeforeEvent = new(), VisibleDurationBeginEvent = new(), VisibleDurationEndEvent = new(); [Content, SerializeField] private DurationStats VisibleDurationStats = default; [Header(nameof(InteractiveDuration))] [Content, SerializeField] private Vector2 InteractiveDuration; public static float DefaultInteractiveLength = IdentifiableJudgementTerminal * 9; [Content, SerializeField] public UnityEvent InteractiveDurationBeforeEvent = new(), InteractiveDurationBeginEvent = new(), InteractiveDurationEndEvent = new(); [Content, SerializeField] private DurationStats InteractiveDurationStats = default; [Header(nameof(InteractableScoreInterval))] [Content, SerializeField] private Vector2 InteractableScoreInterval; public static float DefaultInteractableScoreIntervalLength = IdentifiableJudgementTerminal * 6; [Content, SerializeField] public UnityEvent InteractableScoreIntervalBeforeEvent = new(), InteractableScoreIntervalBeginEvent = new(), InteractableScoreIntervalEndEvent = new(); [Content, SerializeField] private DurationStats InteractableScoreIntervalStats = default; [Header(nameof(InteractableIntervalThatCanScoreBest))] [Content, SerializeField] private Vector2 InteractableIntervalThatCanScoreBest; public static float DefaultInteractableIntervalLengthThatCanScoreBest = IdentifiableJudgementTerminal * 3; [Content, SerializeField] public UnityEvent InteractableIntervalThatCanScoreBestBeforeEvent = new(), InteractableIntervalThatCanScoreBestBeginEvent = new(), InteractableIntervalThatCanScoreBestEndEvent = new(); [Content, SerializeField] private DurationStats InteractableIntervalThatCanScoreBestStats = default; [Header("Judgement")] [Content, SerializeField] private float BestJudgementTimePoint = -1; [Content, SerializeField] public UnityEvent JudgementEvent = new(); public float GetBestJudgementTimePoint() { return BestJudgementTimePoint; } public bool IsInInteractiveDuration() { return InteractiveDurationStats == DurationStats.BeforeEnd; } public bool IsInInteractiveDuration(float time) { return time > InteractiveDuration.x && time < InteractiveDuration.y; } public const int JudgementLevelCount = 3; public override IEnumerator LoadScript(string script) { yield return base.LoadScript(script); if (BestJudgementTimePoint <= 0) { DoSetupJudgement(0); } } public override IEnumerator UnloadScript() { yield return base.UnloadScript(); VisibleDurationBeginEvent = new(); VisibleDurationEndEvent = new(); InteractiveDurationBeginEvent = new(); InteractiveDurationEndEvent = new(); InteractableScoreIntervalBeginEvent = new(); InteractableScoreIntervalEndEvent = new(); InteractableIntervalThatCanScoreBestBeginEvent = new(); InteractableIntervalThatCanScoreBestEndEvent = new(); BestJudgementTimePoint = -1; } public void InvokeJudgement(JudgementLevel level) { if (level == JudgementLevel.None) return; JudgementEvent.Invoke(level); VisibleDurationStats = DurationStats.Judgement; VisibleDurationEndEvent.Invoke(); InteractiveDurationStats = DurationStats.Judgement; InteractableScoreIntervalStats = DurationStats.Judgement; InteractableIntervalThatCanScoreBestStats = DurationStats.Judgement; InteractiveDurationEndEvent.Invoke(); InteractableScoreIntervalEndEvent.Invoke(); InteractableIntervalThatCanScoreBestEndEvent.Invoke(); } private static DurationStats UpdateDurationStats(float begin, UnityEvent beforeEvent, UnityEvent beginEvent, float end, UnityEvent endEvent, float current, DurationStats currentStats) { if (currentStats == DurationStats.After) return DurationStats.After; else if (current < begin) { if (currentStats != DurationStats.BeforeBegin) beforeEvent.Invoke(); return DurationStats.BeforeBegin; } else if (current < end) { if (currentStats == DurationStats.BeforeBegin) beginEvent.Invoke(); return DurationStats.BeforeEnd; } else if (currentStats == DurationStats.BeforeEnd) { endEvent.Invoke(); } return DurationStats.After; } public enum JudgementLevel { None = -2,//No Judge Default = -1, BestLevel = 0,//Level0 ScoreLevel = 1,//Level1 Bad = 128, } public abstract JudgementLevel JudgementBehaviour(float timePoint); private void DoUpdateJudgementStats(float currentTime) { // 可见区间 VisibleDurationStats = UpdateDurationStats(VisibleDuration.x, VisibleDurationBeforeEvent, VisibleDurationBeginEvent, VisibleDuration.y, VisibleDurationEndEvent, currentTime, VisibleDurationStats); if (VisibleDurationStats != DurationStats.BeforeEnd) return; // 可判定区间 InteractiveDurationStats = UpdateDurationStats(InteractiveDuration.x, InteractiveDurationBeforeEvent, InteractiveDurationBeginEvent, InteractiveDuration.y, InteractiveDurationEndEvent, currentTime, InteractiveDurationStats); if (InteractiveDurationStats != DurationStats.BeforeEnd) return; // 1级判定区间 InteractableScoreIntervalStats = UpdateDurationStats(InteractableScoreInterval.x, InteractableScoreIntervalBeforeEvent, InteractableScoreIntervalBeginEvent, InteractableScoreInterval.y, InteractableScoreIntervalEndEvent, currentTime, InteractableScoreIntervalStats); if (InteractableScoreIntervalStats != DurationStats.BeforeEnd) return; // 0级判定区间 InteractableIntervalThatCanScoreBestStats = UpdateDurationStats(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBestBeforeEvent, InteractableIntervalThatCanScoreBestBeginEvent, InteractableIntervalThatCanScoreBest.y, InteractableIntervalThatCanScoreBestEndEvent, currentTime, InteractableIntervalThatCanScoreBestStats); } public override void ResetEnterGameStatus() { base.ResetEnterGameStatus(); DoUpdateJudgementStats(Mathf.NegativeInfinity); } protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType) { base.UpdateTicks(currentTime, deltaTime, tickType); // 预判断 if (VisibleDurationStats == DurationStats.Judgement) return; // 检定 InvokeJudgement(JudgementBehaviour(currentTime)); // 更新状态 DoUpdateJudgementStats(currentTime); } public void DoSetupJudgementLevels( float bestJudgementTimePoint, float interactableIntervalThatCanScoreBest, float interactableScoreInterval, float interactiveDuration, float visibleDuration) { this.BestJudgementTimePoint = bestJudgementTimePoint; var bestJudgementTimePointValue = bestJudgementTimePoint; // InteractableIntervalThatCanScoreBest var interactableIntervalThatCanScoreBestValue = interactableIntervalThatCanScoreBest * 0.5f; InteractableIntervalThatCanScoreBest = new(bestJudgementTimePointValue - interactableIntervalThatCanScoreBestValue, bestJudgementTimePointValue + interactableIntervalThatCanScoreBestValue); // InteractableScoreInterval var interactableScoreIntervalValue = interactableScoreInterval * 0.5f; InteractableScoreInterval = new(bestJudgementTimePointValue - interactableScoreIntervalValue, bestJudgementTimePointValue + interactableScoreIntervalValue); // InteractiveDuration var interactiveDurationValue = interactiveDuration * 0.5f; InteractiveDuration = new(bestJudgementTimePointValue - interactiveDurationValue, bestJudgementTimePointValue + interactiveDurationValue); // InteractableScoreInterval var visibleDurationValue = visibleDuration * 0.5f; VisibleDuration = new(bestJudgementTimePointValue - visibleDurationValue, bestJudgementTimePointValue + visibleDurationValue); } public void DoSetupJudgement(float bestJudgementTimePoint) { DoSetupJudgementLevels( bestJudgementTimePoint, DefaultInteractableIntervalLengthThatCanScoreBest, DefaultInteractableScoreIntervalLength, DefaultInteractiveLength, DefaultVisibleLength); } /// /// 使用配置中的区间长度设置以最佳判定点为中心的各级区间 /// /// 最佳判定点 [ScriptableCall(@" 使用配置中的区间长度设置以最佳判定点为中心的各级区间 最佳判定点 ")] public void SetupJudgement(string bestJudgementTimePoint) { DoSetupJudgement(Parse(bestJudgementTimePoint)); } /// /// 通过传递对称区间进行初始化 /// /// 最佳判定点 /// 区间时长,最终结果为 /// (bestJudgementTimePoint-interactableIntervalThatCanScoreBest/2,bestJudgementTimePoint+interactableIntervalThatCanScoreBest/2) /// 区间时长,最终结果为 /// (bestJudgementTimePoint-interactableScoreInterval/2,bestJudgementTimePoint+interactableScoreInterval/2) /// 区间时长,最终结果为 /// (bestJudgementTimePoint-interactiveDuration/2,bestJudgementTimePoint+interactiveDuration/2) /// 区间时长,最终结果为 /// (bestJudgementTimePoint-visibleDuration/2,bestJudgementTimePoint+visibleDuration/2) [ScriptableCall(@" 通过传递对称区间进行初始化 最佳判定点 区间时长,最终结果为 (bestJudgementTimePoint-interactableIntervalThatCanScoreBest/2,bestJudgementTimePoint+interactableIntervalThatCanScoreBest/2) 区间时长,最终结果为 (bestJudgementTimePoint-interactableScoreInterval/2,bestJudgementTimePoint+interactableScoreInterval/2) 区间时长,最终结果为 (bestJudgementTimePoint-interactiveDuration/2,bestJudgementTimePoint+interactiveDuration/2) 区间时长,最终结果为 (bestJudgementTimePoint-visibleDuration/2,bestJudgementTimePoint+visibleDuration/2) ")] public void SetupJudgementLevels( string bestJudgementTimePoint, string interactableIntervalThatCanScoreBest, string interactableScoreInterval, string interactiveDuration, string visibleDuration) { DoSetupJudgementLevels( Parse(bestJudgementTimePoint), Parse(interactableIntervalThatCanScoreBest), Parse(interactableScoreInterval), Parse(interactiveDuration), Parse(visibleDuration)); } /// /// 设置可见区间(显现但不可判定,3级判定区间)开始时间 /// /// [ScriptableCall(@" 设置可见区间(显现但不可判定,3级判定区间)开始时间 ")] public void SetVisibleDurationBegin(string value) { VisibleDuration.x = Parse(value); } /// /// 设置可见区间(显现但不可判定,3级判定区间)结束时间 /// /// [ScriptableCall(@" 设置可见区间(显现但不可判定,3级判定区间)结束时间 ")] public void SetVisibleDurationEnd(string value) { VisibleDuration.y = Parse(value); } /// /// 设置2级判定区间(可判定但错误的)开始时间 /// /// [ScriptableCall(@" 设置2级判定区间(可判定但错误的)开始时间 ")] public void SetInteractiveDurationBegin(string value) { InteractiveDuration.x = Parse(value); } /// /// 设置2级判定区间(可判定但错误的)结束时间 /// /// [ScriptableCall(@" 设置2级判定区间(可判定但错误的)结束时间 ")] public void SetInteractiveDurationEnd(string value) { InteractiveDuration.y = Parse(value); } /// /// 设置1级判定区间(可判定的)开始时间 /// /// [ScriptableCall(@" 设置1级判定区间(可判定的)开始时间 ")] public void SetInteractableScoreIntervalBegin(string value) { InteractableScoreInterval.x = Parse(value); } /// /// 设置1级判定区间(可判定的)结束时间 /// /// [ScriptableCall(@" 设置1级判定区间(可判定的)结束时间 ")] public void SetInteractableScoreIntervalEnd(string value) { InteractableScoreInterval.y = Parse(value); } /// /// 设置0级判定区间(最佳判定)开始时间 /// /// [ScriptableCall(@" 设置0级判定区间(最佳判定)开始时间 ")] public void SetInteractableIntervalThatCanScoreBestBegin(string value) { InteractableIntervalThatCanScoreBest.x = Parse(value); if (BestJudgementTimePoint < 0) { BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f); } } /// /// 设置0级判定区间(最佳判定)结束时间 /// /// [ScriptableCall(@" 设置0级判定区间(最佳判定)结束时间 ")] public void SetInteractableIntervalThatCanScoreBestEnd(string value) { InteractableIntervalThatCanScoreBest.y = Parse(value); if (BestJudgementTimePoint < 0) { BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f); } } } }