using System; using System.Collections; using System.IO; using Convention; using Demo.Editor.UI; using UnityEngine; using UnityEngine.Events; namespace Demo.Game { namespace ConfigType { // IInteraction 配置(抽象基类Config,继承自TimelineScriptObject) public class IInteractionConfig : ScriptLoadableConfig { public Vector2 VisibleDuration; public Vector2 InteractiveDuration; public Vector2 InteractableScoreInterval; public Vector2 InteractableIntervalThatCanScoreBest; public IInteraction.JudgementLevel MyJudgementLevel; public IInteraction.UpdatePhase MyUpdatePhase; public override void Deserialize(BinaryReader reader) { VisibleDuration = BinarySerializeUtility.ReadVec2(reader); InteractiveDuration = BinarySerializeUtility.ReadVec2(reader); InteractableScoreInterval = BinarySerializeUtility.ReadVec2(reader); InteractableIntervalThatCanScoreBest = BinarySerializeUtility.ReadVec2(reader); MyJudgementLevel = (IInteraction.JudgementLevel)BinarySerializeUtility.ReadInt(reader); MyUpdatePhase = (IInteraction.UpdatePhase)BinarySerializeUtility.ReadInt(reader); base.Deserialize(reader); } public override void Serialize(BinaryWriter writer) { BinarySerializeUtility.WriteVec2(writer, VisibleDuration); BinarySerializeUtility.WriteVec2(writer, InteractiveDuration); BinarySerializeUtility.WriteVec2(writer, InteractableScoreInterval); BinarySerializeUtility.WriteVec2(writer, InteractableIntervalThatCanScoreBest); BinarySerializeUtility.WriteInt(writer, (int)MyJudgementLevel); BinarySerializeUtility.WriteInt(writer, (int)MyUpdatePhase); base.Serialize(writer); } } } public interface IHookInteraction { } public abstract class IInteraction : TimelineScriptObject { protected override void SetupTimelineItem(TimelineItem item) { item.SetupDuration(new(VisibleDuration.x, VisibleDuration.y), GetTimelineItemColor()); } public const float IdentifiableJudgementTerminal = 0.16f; [Content, SerializeField, Header("Cache")] public float MyCacheUpdateTick = 0; [Header(nameof(VisibleDuration))] [Content, SerializeField] private Vector2 VisibleDuration; public static float DefaultVisibleLength = IdentifiableJudgementTerminal * 15; [Content, SerializeField] public UnityEvent VisibleDurationBeforeEvent = new(), VisibleDurationBeginEvent = new(), VisibleDurationEndEvent = new(); [Header(nameof(InteractiveDuration))] [Content, SerializeField] private Vector2 InteractiveDuration; public static float DefaultInteractiveLength = IdentifiableJudgementTerminal * 9; [Content, SerializeField] public UnityEvent InteractiveDurationBeforeEvent = new(), InteractiveDurationBeginEvent = new(), InteractiveDurationEndEvent = new(); [Header(nameof(InteractableScoreInterval))] [Content, SerializeField] private Vector2 InteractableScoreInterval; public static float DefaultInteractableScoreIntervalLength = IdentifiableJudgementTerminal * 6; [Content, SerializeField] public UnityEvent InteractableScoreIntervalBeforeEvent = new(), InteractableScoreIntervalBeginEvent = new(), InteractableScoreIntervalEndEvent = new(); [Header(nameof(InteractableIntervalThatCanScoreBest))] [Content, SerializeField] private Vector2 InteractableIntervalThatCanScoreBest; public static float DefaultInteractableIntervalLengthThatCanScoreBest = IdentifiableJudgementTerminal * 3; [Content, SerializeField] public UnityEvent InteractableIntervalThatCanScoreBestBeforeEvent = new(), InteractableIntervalThatCanScoreBestBeginEvent = new(), InteractableIntervalThatCanScoreBestEndEvent = new(); [Flags,Serializable] public enum UpdatePhaseComponent { OutsideVisibleDuration = 0b00000000, // InsideVisibleDuration = 0b00000001, InsideInteractiveDuration = 0b00000010, InsideInteractableScoreInterval = 0b00000100, InsideInteractableIntervalThatCanScoreBest = 0b00001000, // EnterVisibleDuration = 0b00010000, EnterInteractiveDuration = 0b00100000, EnterInteractableScoreInterval = 0b01000000, EnterInteractableIntervalThatCanScoreBest = 0b10000000, } [Flags, Serializable] public enum UpdatePhase { // 进入可视区间之前 BeforeVisibleDuration = UpdatePhaseComponent.OutsideVisibleDuration, // 进入不可判定但可视的3级判定区间 EnterVisibleDuration = UpdatePhaseComponent.InsideVisibleDuration | UpdatePhaseComponent.EnterVisibleDuration, // 进入可判定为失误的2级判定区间 EnterInteractiveDuration = UpdatePhaseComponent.InsideInteractiveDuration | UpdatePhaseComponent.EnterInteractiveDuration | UpdatePhase.EnterVisibleDuration, // 进入可得分的1级判定区间 EnterInteractableScoreInterval = UpdatePhaseComponent.InsideInteractableScoreInterval | UpdatePhaseComponent.EnterInteractableScoreInterval | UpdatePhase.EnterInteractiveDuration, // 进入可得满分的0级判定区间 EnterInteractableIntervalThatCanScoreBest = UpdatePhaseComponent.InsideInteractableIntervalThatCanScoreBest| UpdatePhaseComponent.EnterInteractableIntervalThatCanScoreBest | UpdatePhase.EnterInteractableScoreInterval, // 退出0级判定区间 AfterInteractableIntervalThatCanScoreBest = UpdatePhase.EnterInteractableIntervalThatCanScoreBest&(~UpdatePhaseComponent.InsideInteractableIntervalThatCanScoreBest), // 退出1级判定区间 AfterInteractableScoreInterval = UpdatePhase.AfterInteractableIntervalThatCanScoreBest & (~UpdatePhaseComponent.InsideInteractableScoreInterval), // 退出2级判断区间 AfterInteractiveDuration = UpdatePhase.AfterInteractableScoreInterval & (~UpdatePhaseComponent.InsideInteractiveDuration), // 退出3级判断区间, 已不可视 AfterVisibleDuration = UpdatePhase.AfterInteractiveDuration & (~UpdatePhaseComponent.InsideVisibleDuration) } public enum JudgementLevel { None = -2,//No Judge Default = -1, BestLevel = 0,//Level0 ScoreLevel = 1,//Level1 Bad = 128, } [Header("Judgement")] [Content, SerializeField] private JudgementLevel MyJudgementLevel = JudgementLevel.None; [Content, SerializeField] private UpdatePhase MyUpdatePhase = default; [Content, SerializeField] private float BestJudgementTimePoint = -1; [Content, SerializeField] public UnityEvent JudgementEvent = new(); public float GetBestJudgementTimePoint() { return BestJudgementTimePoint; } public bool IsInInteractiveDuration(float time) { return time > InteractiveDuration.x && time < InteractiveDuration.y; } public bool IsInInteractiveDuration() { return IsInInteractiveDuration(MyCacheUpdateTick); } public const int JudgementLevelCount = 3; 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; // 进入已判定状态 MyJudgementLevel = level; JudgementEvent.Invoke(level); // 触发还未触发的退出事件 { if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterInteractableIntervalThatCanScoreBest) != 0) { InteractableIntervalThatCanScoreBestEndEvent.Invoke(); } if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterInteractableScoreInterval) != 0) { InteractableScoreIntervalEndEvent.Invoke(); } if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterInteractiveDuration) != 0) { InteractiveDurationEndEvent.Invoke(); } if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterVisibleDuration) != 0) { VisibleDurationEndEvent.Invoke(); } } } public abstract JudgementLevel JudgementBehaviour(float timePoint); #region Update Judgement Stats private void DoUpdateJudgementStats(float currentTime) { // 重置 if (MyCacheUpdateTick > currentTime) { MyUpdatePhase = UpdatePhase.BeforeVisibleDuration; VisibleDurationBeforeEvent.Invoke(); InteractiveDurationBeforeEvent.Invoke(); InteractableScoreIntervalBeforeEvent.Invoke(); InteractableIntervalThatCanScoreBestBeforeEvent.Invoke(); MyJudgementLevel = JudgementLevel.None; } // 状态更新 { // 在当前区间进行判断, 当前时间落入下一个区间时, 就进入下一个区间 switch (MyUpdatePhase) { case UpdatePhase.BeforeVisibleDuration: if (currentTime > VisibleDuration.x) { MyUpdatePhase = UpdatePhase.EnterVisibleDuration; VisibleDurationBeginEvent.Invoke(); } break; case UpdatePhase.EnterVisibleDuration: if (currentTime > InteractiveDuration.x) { MyUpdatePhase = UpdatePhase.EnterInteractiveDuration; InteractiveDurationBeginEvent.Invoke(); } break; case UpdatePhase.EnterInteractiveDuration: if (currentTime > InteractableScoreInterval.x) { MyUpdatePhase = UpdatePhase.EnterInteractableScoreInterval; InteractableScoreIntervalBeginEvent.Invoke(); } break; case UpdatePhase.EnterInteractableScoreInterval: if (currentTime > InteractableIntervalThatCanScoreBest.x) { MyUpdatePhase = UpdatePhase.EnterInteractableIntervalThatCanScoreBest; InteractableIntervalThatCanScoreBestBeginEvent.Invoke(); } break; // --------------------------------------------------------- case UpdatePhase.EnterInteractableIntervalThatCanScoreBest: if (currentTime > InteractableIntervalThatCanScoreBest.y) { MyUpdatePhase = UpdatePhase.AfterInteractableIntervalThatCanScoreBest; InteractableIntervalThatCanScoreBestEndEvent.Invoke(); } break; case UpdatePhase.AfterInteractableIntervalThatCanScoreBest: if (currentTime > InteractableScoreInterval.y) { MyUpdatePhase = UpdatePhase.AfterInteractableScoreInterval; InteractableScoreIntervalEndEvent.Invoke(); } break; case UpdatePhase.AfterInteractableScoreInterval: if (currentTime > InteractiveDuration.y) { MyUpdatePhase = UpdatePhase.AfterInteractiveDuration; InteractiveDurationEndEvent.Invoke(); } break; case UpdatePhase.AfterInteractiveDuration: if (currentTime > VisibleDuration.y) { MyUpdatePhase = UpdatePhase.AfterVisibleDuration; VisibleDurationEndEvent.Invoke(); } break; default: break; } } // 记录 MyCacheUpdateTick = currentTime; } #endregion protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType) { base.UpdateTicks(currentTime, deltaTime, tickType); // 检定 if (MyJudgementLevel == JudgementLevel.None) { 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); } /// /// 使用配置中的区间长度设置以最佳判定点为中心的各级区间 /// /// 最佳判定点 [Convention.RScript.Variable.Attr.Method] public void SetupJudgement(float bestJudgementTimePoint) { DoSetupJudgement(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) [Convention.RScript.Variable.Attr.Method] public void SetupJudgementLevels( float bestJudgementTimePoint, float interactableIntervalThatCanScoreBest, float interactableScoreInterval, float interactiveDuration, float visibleDuration) { DoSetupJudgementLevels( bestJudgementTimePoint, interactableIntervalThatCanScoreBest, interactableScoreInterval, interactiveDuration, visibleDuration); } /// /// 设置可见区间(显现但不可判定,3级判定区间)开始时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetVisibleDurationBegin(float value) { VisibleDuration.x = value; } /// /// 设置可见区间(显现但不可判定,3级判定区间)结束时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetVisibleDurationEnd(float value) { VisibleDuration.y = value; } /// /// 设置2级判定区间(可判定但错误的)开始时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetInteractiveDurationBegin(float value) { InteractiveDuration.x = value; } /// /// 设置2级判定区间(可判定但错误的)结束时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetInteractiveDurationEnd(float value) { InteractiveDuration.y = value; } /// /// 设置1级判定区间(可判定的)开始时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetInteractableScoreIntervalBegin(float value) { InteractableScoreInterval.x = value; } /// /// 设置1级判定区间(可判定的)结束时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetInteractableScoreIntervalEnd(float value) { InteractableScoreInterval.y = value; } /// /// 设置0级判定区间(最佳判定)开始时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetInteractableIntervalThatCanScoreBestBegin(float value) { InteractableIntervalThatCanScoreBest.x = value; if (BestJudgementTimePoint < 0) { BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f); } } /// /// 设置0级判定区间(最佳判定)结束时间 /// /// [Convention.RScript.Variable.Attr.Method] public void SetInteractableIntervalThatCanScoreBestEnd(float value) { InteractableIntervalThatCanScoreBest.y = value; if (BestJudgementTimePoint < 0) { BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f); } } } }